返回介绍

第 12 章 网络编程

发布于 2025-02-19 23:42:47 字数 12332 浏览 0 评论 0 收藏 0

虽然书中许多示例侧重于读取文件和检索文件中的数据,而如今的互联网上有丰富的信息来源,值得考虑在内。

在本章中,我们伪装成一个网络浏览器,使用超文本传输协议(HyperText Transport Protocol, HTTP)检索网页,读取页面数据并进行解析。

12.1 超文本传输协议 — HTTP

网络协议驱动了整个网络,其本身非常简单。由于 Python 内置了 sockets 库,在 Python 程序中建立网络连接,通过这些套接字检索数据,变得非常容易。

套接字很像文件。不同的是它提供了两个程序之间的双向连接,在一个套接字上可以同时读取和写入。如果你在套接字的一端编写内容,套接字会把数据发送给另一端的应用程序。如果从套接字读取,将得到另一个程序发送的数据。

然而,当套接字另一端没有发送任何数据时,如果你尝试读取套接字,结果就只能等待。如果套接字两端的程序都在等待数据,而不发送任何数据,它们就会这样僵持下去。

程序的一个重要组成部分是与互联网的通讯,即具备某种协议。协议是一组精确规则的集合,决定了谁先谁后,做些什么,对消息如何响应,以及下一步谁来发送等。从某种意义上说,套接字两端的两个程序像是在跳舞,确保不会踩到对方的脚趾。

已有大量文档介绍网络协议。超文本传输协议原文如下:

http://www.w3.org/Protocols/rfc2616/rfc2616.txt

这个文档包含 179 页,内容复杂详尽。如果你感兴趣,请读完它。如果仅是翻看 RFC2616 的第 36 页左右,你会找到 GET 请求的语法。如果仔细阅读,你会发现是从网络服务器请求文档,与 http://www.py4inf.com 服务器的 80 端口建立连接,然后发送表单的一行。

GET http://www.py4inf.com/code/romeo.txt HTTP/1.0

第二个参数是我们请求的网页,随后我们发送一个空白行。网络服务器将响应文档的一些头部信息和文档内容之后的空白行。

12.2 世界上最简单的网络浏览器

解释 HTTP 工作原理的最简单方法,也许就是编写一段非常简单的 Python 程序。与网络服务器建立连接,遵循 HTTP 协议规则,向服务器请求文档并显示出来。

import socket

mysock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
mysock.connect(('www.py4inf.com', 80))
mysock.send('GET http://www.py4inf.com/code/romeo.txt HTTP/1.0\n\n')

while True:
  data = mysock.recv(512)
  if ( len(data) < 1 ) :
    break
  print data

mysock.close()

首先,程序与服务器 http://www.py4inf.com 在 80 端口建立一个连接,这个程序扮演了网络浏览器的角色。HTTP 协议要求,必须发送 GET 命令,并在后面跟一个空白行。

enter image description here

发送空白行之后,我们编写一个循环,从套接字中接收 512 个字符的数据片段,并打印出这些数据,直到没有数据可以读入,即 recv() 返回一个空字符串。

程序运行结果如下:

HTTP/1.1 200 OK
Date: Sun, 14 Mar 2010 23:52:41 GMT
Server: Apache
Last-Modified: Tue, 29 Dec 2009 01:31:22 GMT
ETag: "143c1b33-a7-4b395bea"
Accept-Ranges: bytes
Content-Length: 167
Connection: close
Content-Type: text/plain

But soft what light through yonder window breaks
It is the east and Juliet is the sun
Arise fair sun and kill the envious moon
Who is already sick and pale with grief

程序一开始输出的是网络服务器发送的文档描述的头部信息。例如,Content-Type 头部指明文档是一个普通文本文档(text/plain)。

服务器发送了头部信息之后,添加一个空白行表示头部信息发送完毕,然后发送实际的 romeo.txt 文件数据。

示例展示了如何通过套接字建立低级别的网络连接。套接字用于网络服务器、邮件服务器以及其他许多类型服务器的通讯。找到描述协议的文档,编写代码,根据协议发送和获取数据,要做的就这么多了。

由于最常用的协议是 HTTP(即 Web)协议,Python 针对 HTTP 协议设计了专门的库来支持网络文档数据的获取。

12.3 通过 HTTP 检索图像

在以上示例中,我们获取了一个包含换行符的普通文本文件,程序运行时简单地拷贝数据并显示出来。接下来,我们使用 HTTP 编写一个类似的程序,用来检索图片。在一个字符串中累积数据,截取头部信息,然后将图片数据保存到一个文件中,程序代码如下:

import socket
import time

mysock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
mysock.connect(('www.py4inf.com', 80))
mysock.send('GET http://www.py4inf.com/cover.jpg HTTP/1.0\n\n')


count = 0
picture = "";
while True:
  data = mysock.recv(5120)
  if ( len(data) < 1 ) : break
  # time.sleep(0.25)
  count = count + len(data)
  print len(data),count
  picture = picture + data

mysock.close()

# Look for the end of the header (2 CRLF)
pos = picture.find("\r\n\r\n");
print 'Header length',pos
print picture[:pos]

# Skip past the header and save the picture data
picture = picture[pos+4:]
fhand = open("stuff.jpg","wb")
fhand.write(picture);
fhand.close()

程序运行结果如下:

$ python urljpeg.py
2920 2920
1460 4380
1460 5840
1460 7300
...
1460 62780
1460 64240
2920 67160
1460 68620
1681 70301
Header length 240
HTTP/1.1 200 OK
Date: Sat, 02 Nov 2013 02:15:07 GMT
Server: Apache
Last-Modified: Sat, 02 Nov 2013 02:01:26 GMT
ETag: "19c141-111a9-4ea280f8354b8"
Accept-Ranges: bytes
Content-Length: 70057
Connection: close
Content-Type: image/jpeg

针对这个 url,Content-Type 头部指明文档本身是一个图像(img/jpeg)。程序运行完毕之后,使用图像浏览器打开 stuff.jpg 文件查看图像数据。

程序运行中调用了 recv() 函数,每次不会得到 5120 个字符。调用 recv() 时,通过网络,我们从网络服务器获得更多字符串。在这个示例中,每一次获得 1460 或 2920 个字符,请求上限是 5120 个字符。

网速不同会导致不同的结果。还要注意的是,最后一次调用 recv(),在数据流结束时得到 1681 个字节,再下一个 recv() 调用获得零长度的字符串。这就是告诉我们,服务器已经在套接字末尾调用了 close(),没有更多数据可发送了。

把 time.sleep() 前面的注释去掉,这样可以减缓随后的调用。这样一来,每次相隔 1/4 秒,服务器让我们“靠前”,发送更多的数据。程序的延时间隔执行如下所示:

$ python urljpeg.py
1460 1460
5120 6580
5120 11700
...
5120 62900
5120 68020
2281 70301
Header length 240
HTTP/1.1 200 OK
Date: Sat, 02 Nov 2013 02:22:04 GMT
Server: Apache
Last-Modified: Sat, 02 Nov 2013 02:01:26 GMT
ETag: "19c141-111a9-4ea280f8354b8"
Accept-Ranges: bytes
Content-Length: 70057
Connection: close
Content-Type: image/jpeg

这次运行中除了第一次和最后一次 recv() 调用,每次请求新数据都会得到 5120 个字符。

服务器生成的 send() 请求与程序生成的 recv() 请求之间存在一个缓冲区。当程序执行延迟请求时,在某些点上,服务器可能会在套接字中填满缓冲区,并强制暂停,直到程序开始清空缓存区。发送应用或接收应用的暂停行为被称为“流量控制”。

12.4 使用 urllib 检索网页

与通过 HTTP 套接字库手动发送与获取数据相比,Python 中有一种更简单的解决方法,使用 urllib 库。

使用 urllib,你可以将网页看成一个文件。只需简单指明需要检索的网页,urllib 会处理所有 HTPP 协议和头部细节。

使用 urllib 读取 romeo.txt 文件的代码如下:

import urllib

fhand = urllib.urlopen('http://www.py4inf.com/code/romeo.txt')
for line in fhand:
   print line.strip()

网页通过 urllib.urlopen 打开后,我们就可以把它当成一个文件,使用 for 循环来读取。

程序运行时,我们仅看到文件内容的输出。虽然头部信息仍然会发送,但是 urllib 代码会处理头部,发送给我们的仅是文件内容。

But soft what light through yonder window breaks
It is the east and Juliet is the sun
Arise fair sun and kill the envious moon
Who is already sick and pale with grief

为了演示说明,我们编写一个程序来获取 romeo.txt 的数据,计算文件中每个单词的频率,代码如下所示:

import urllib

counts = dict()
fhand = urllib.urlopen('http://www.py4inf.com/code/romeo.txt')
for line in fhand:
  words = line.split()
  for word in words:
    counts[word] = counts.get(word,0) + 1
print counts

同样地,一旦打开了网页,我们就可以把它当做一个本地文件进行读取。

12.5 解析 HTML 和 Web 抓取

Python 的 urllib 常见用法之一是网页抓取。网页抓取是编写一个程序,伪装成网络浏览器,检索网页,然后在这些页面中根据模式检视数据。

举例来说,搜索引擎(如 Google)会查看网页的源代码,抽取链接到其他页面的超链接,然后检索这些页面,抽取超链接,如此往复下去。使用这种技术,Google 爬虫几乎遍历了网络上的所有页面。

Google 还使用页面链接的频次来说明一个具体页面的重要性,在搜索结果中表明页面排位的高低。

12.6 使用正则表达式解析 HTML

HTML 解析的一个简单方法是使用正则表达式进行重复搜索,根据特定模式,抽取出与之匹配的子字符串。

以下是一个简单的网页:

<h1>The First Page</h1>
<p>
If you like, you can switch to the
<a href="http://www.dr-chuck.com/page2.htm">
Second Page</a>.
</p>

我们构造一个符合语法规则的正则表达式,匹配和抽取以上网页文本中的超链接,正则表达式如下所示:

href="http://.+?"

这个正则表达式查找以 ref="http:// 开头的字符串,之后是一个或多个字符“.+?”,最后是另一个双引号。“.+?”表示以非贪婪方式进行匹配,而不是贪婪方式。非贪婪匹配试图找到最小可能匹配的字符串,贪婪匹配试图找到最大可能匹配的字符串。

正则表达式中的括号表示,我们需要精确匹配的字符串,程序代码如下:

import urllib
import re

url = raw_input('Enter - ')
html = urllib.urlopen(url).read()
links = re.findall('href="(http://.*?)"', html)
for link in links:
  print link

正则表达式的 findall 方法返回正则表达式匹配到的字符串列表,仅返回双引号之间的超链接文本。程序运行结果如下:

python urlregex.py
Enter - http://www.dr-chuck.com/page1.htm
http://www.dr-chuck.com/page2.htm

python urlregex.py
Enter - http://www.py4inf.com/book.htm
http://www.greenteapress.com/thinkpython/thinkpython.html
http://allendowney.com/
http://www.py4inf.com/code
http://www.lib.umich.edu/espresso-book-machine
http://www.py4inf.com/py4inf-slides.zip

如果 HTML 网页是良构的和可预测的,正则表达式会处理地很漂亮。但是,由于存在大量“破坏性”的 HTML 网页,你可能会发现,仅使用正则表达式的解决方案可能会错过一些有效链接或中止于“坏数据”。

强大的 HTML 解析库可以解决这个问题。

12.7 使用 BeautifulSoup 解析 HTML

已有许多 Python 库可以帮助你解析 HTML 与抽取页面中的数据。每个 Python 库都有优缺点,根据需要进行选择。

举例来看,我们使用 BeautifulSoup 来简单解析一些 HTML 输入和抽取链接。从 http://www.crummy.com 网站下载和安装 BeautifulSoup 代码。

你可以下载和安装 BeautifulSoup,或简单地将 BeautifulSoup.py 放在你的程序文件夹下。

HTML 与 XML 看起来很像,有一些网页被精心构造为 XML。一般而言,大多数 HTML 会被 XML 解析器认为是格式不正确而整体拒绝,导致解析失败。BeautifulSoup 极大容忍了 HTML 的缺陷,依然让你能轻易抽取所需的数据。

我们使用 urllib 读取页面,然后根据锚标签抽取 href 属性的内容。

import urllib
from BeautifulSoup import *

url = raw_input('Enter - ')
html = urllib.urlopen(url).read()
soup = BeautifulSoup(html)

# Retrieve all of the anchor tags
tags = soup('a')
for tag in tags:
   print tag.get('href', None)

该程序提示输入一个网址,然后打开网页,读取与传递数据到 BeautifulSoup 解析器,接下来,检索所有的锚标签,打印出每个标签的 href 属性内容。

python urllinks.py
Enter - http://www.dr-chuck.com/page1.htm
http://www.dr-chuck.com/page2.htm

python urllinks.py
Enter - http://www.py4inf.com/book.htm
http://www.greenteapress.com/thinkpython/thinkpython.html
http://allendowney.com/
http://www.si502.com/
http://www.lib.umich.edu/espresso-book-machine
http://www.py4inf.com/code
http://www.pythonlearn.com/

使用 BeautifulSoup 取出每个标签的不同部分,代码如下:

import urllib
from BeautifulSoup import *

url = raw_input('Enter - ')
html = urllib.urlopen(url).read()
soup = BeautifulSoup(html)

# Retrieve all of the anchor tags
tags = soup('a')
for tag in tags:
   # Look at the parts of a tag
   print 'TAG:',tag
   print 'URL:',tag.get('href', None)
   print 'Content:',tag.contents[0]
   print 'Attrs:',tag.attrs

程序运行结果如下:

python urllink2.py
Enter - http://www.dr-chuck.com/page1.htm
TAG: <a href="http://www.dr-chuck.com/page2.htm">
Second Page</a>
URL: http://www.dr-chuck.com/page2.htm
Content: [u'\nSecond Page']
Attrs: [(u'href', u'http://www.dr-chuck.com/page2.htm')]

这些例子仅揭开了 BeautifulSoup 解析 HTML 功能的冰山一角。更多内容详见 http://www.crummy.com 文档与示例。

12.8 使用 urllib 读取二进制文件

有时,你需要检索非文本(二进制)文件,如图像或视频文件。这些文件的数据直接打印输出是没有用的,但可以通过 urllib 将 URL 指向的文件保存在本地硬盘。

该模式打开 URL,使用 read 方法下载整个文档内容,将其存入一个字符串变量(如 img),然后将变量内容写入本地文件。程序代码如下:

img = urllib.urlopen('http://www.py4inf.com/cover.jpg').read()
fhand = open('cover.jpg', 'w')
fhand.write(img)
fhand.close()

该程序通过网络读取所有数据,将它存在计算机内存的 img 变量中,然后打开文件 cover.jpg,将数据写入到硬盘中。如果文件大小小于计算机内存容量,这个程序将会成功运行。

然而,如果是一个大型音频或视频文件,当计算机耗尽内存时,这个程序可能会崩溃或运行极为缓慢。为了避免耗尽内存,我们以区块(或缓冲区)检索数据,在检索下一个区块前将当前区块写入磁盘。这样一来,程序就可以读取任意大小的文件,无需担心耗尽计算机的全部内存。

import urllib

img = urllib.urlopen('http://www.py4inf.com/cover.jpg')
fhand = open('cover.jpg', 'w')
size = 0
while True:
  info = img.read(100000)
  if len(info) < 1 : break
  size = size + len(info)
  fhand.write(info)

print size,'characters copied.'
fhand.close()

在这个示例中,每次读取 100,000 个字符,从网络检索下一批 100,000 个字符数据之前,先将这些字符写入 cover.jpg 文件。

程序运行结果如下:

python curl2.py
568248 characters copied.

如果是 Unix 或 Mac 计算机,你可以使用操作系统内置命令来执行这个操作:

curl -O http://www.py4inf.com/cover.jpg

curl 命令是“copy URL”的缩写,这两个例子文件命名为 curl1.py 和 curl2.py,可以从 http://www.py4inf.com/code 下载。它们实现了 culr 命令相似的功能。curl3.py 示例程序以更高效的方式完成二进制文件的读写,这种模式可能对你自己编写程序时有所帮助。

12.9 术语

BeautifulSoup: 一个用于 HTML 文档解析与数据抽取的 Python 库。它能够处理大多数在浏览器中通常被忽略的,存在缺陷的 HTML。BeautifulSoup 代码 http://从 www.crummy.com 网站下载。

端口: 当与服务器建立套接字连接时,服务器告诉应用程序进行通讯所采用的数字。例如,网络流量通常使用 80 端口,电子邮件流量使用 25 端口。

抓取: 把程序伪装成网络浏览器,检索网页,查找网页中的内容。通常,程序会根据一个网页中的链接找到下一个网页,实现对网页网络或社交网络的遍历。

套接字: 两个应用程序之间的网络连接,彼此可以发送与接收数据。

爬虫: 网络搜索引擎的检索页面,然后从该页面所有链接再次发起检索,如此往复下去,直到它们检索到网络上的几乎所有页面。这些页面将用于构建索引,供搜索之用。

12.10 练习

习题 12.1 修改套接字程序 socket1.py,提示用户输入 URL,让它可以读取任何网页。你可以使用 split(’/’) 拆分 URL,抽取出套接字 connect 调用的主机名。使用 try 和 except 增加错误检查,处理用户输入不恰当的网址或不存在的 URL 这两种情况。

习题 12.2 修改套接字程序,统计它所接收到的字符数,当显示了 3000 个字符之后停止显示其他字符。该程序应检索整个文档,统计字符总数并显示在文档结尾。

习题 12.3 使用 urllib 重复之前的练习:(1)从 URL 中检索文档;(2)显示 3000 个字符;(3)统计文档的字符总数。这里不必担心头部信息,只显示文档内容中前 3000 个字符即可。

习题 12.4 修改 ulrlinks.py 程序,对检索到的 HTML 文档抽取和统计段落标签(p),在程序输出中显示段落数量。不要显示段落文本,仅统计段落总数。在简单网页和复杂网页上测试该程序。

习题 12.5 (进阶)修改 socket 程序,使其只显示头部和空行之后的检索数据。请记住,recv 是按照字符(包括换行及所有)而非行来接收的。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文