上一篇文章大概的讲解了 Python 爬虫的基础架构,我们对 Python 爬虫内部运行流程有了一定的理解了,我们这节将用一些简单的 Python 代码实现Python 爬虫架构的 URL 管理器、网页下载器和网页解析器。
URL 管理器
上篇文章我们已经说了,URL 管理器是用来管理待抓取的 URL 和已抓取的 URL,作为一只聪明的爬虫,我们当然应该会选择跳过那些我们已经爬取过的 URL ,这不仅是为了防止重复抓取,也为了防止一些循环抓取的问题,URL 间的互相调用会导致爬虫的无限死循环抓取。
URL 管理器就是为了解决这些问题而存在的,有了它,我们的爬虫才会更加聪明,从而避免重复抓取和循环抓取。上面列出的就是 URL 管理器所要做的工作,根据这些职能,我们就可以总结出实现 URL 管理器的一个大体思路。
我们需要两个容器 A 和 B,A 用来存储待爬取的 URL,B 用来存储已爬取的 URL,管理器从 A 中获取 URL 来交付给网页下载器去处理,如果 A 中没有 URL 就等待,每当爬虫爬取到新的 URL 的时候,就将这个 URL 添加到 A 中排队等待爬取。爬取完一个 URL 后,就把这个 URL 存放到 B 中。爬虫的时候,如果获取到的 URL 在 A 中或者 B 中存在了,就跳过该 URL。流程图如下:
以上功能是 URL 管理器需要实现功能的最小功能集合,复杂的情况当然具备的功能也更多。我们来看一下简单的 URL 管理器内存实现代码。
class UrlManager(object):
def __init__(self):
self.new_urls = set()
self.old_urls = set()
def add_new_url(self,url):
if url is None:
return
if url not in self.new_urls and url not in self.old_urls:
#添加待爬取URL
self.new_urls.add(url)
def has_new_url(self):
return len(self.new_urls) != 0
def get_new_url(self):
#获取并移除一个待爬取URL
new_url = self.new_urls.pop()
#将URL添加进已爬取URL
self.old_urls.add(new_url)
上面的代码很简单,我们使用 Python 中的 Set 来作为容器管理 URL,因为它可以自动的进行去重处理而且内部的查询速度也是非常快速,所以很方便。获取待爬取 URL 的时候,我们使用 pop 方法,在获取一个元素的同时将它从 set 中移除出去,从而实现类似队列的排队形式。
这就是我们 URL 管理器的一个简单的实现方式了,虽然很简单,但基本功能思想已经包含在里面了。
网页下载器
网页下载器是将互联网上的 URL 对应的网页下载到本地的工具,当我们从 URL 管理器中获取到一个爬取 URL 的时候,我们只有将 URL 对应的网页下载到本地,才能继续后面的数据处理,所以网页下载器在爬虫架构中十分重要,是核心组件。
网页下载器的运行模式很简单,它可以将 URL 对应的网页以 HTML 的形式下载到本地,存储成一个本地文件或者以内存字符串的形式存储下来。总而言之就是下载一个静态网页文件,文件内容就是
这样的标签组成的 HTML 文件。
Python 中实现网页下载器有很多现成并且功能强大的库可供选择。urllib 是 Python 官方提供的基础模块,requests 是一个功能强大的第三方模块,我将使用 Python3 中的 urllib 作为演示。需要注意的是 urllib2 和 Python3 的 urllib 语法区别还是比较大的,大家权益好选择一个版本来进行学习。
#encoding:UTF-8
import urllib.request
url = "http://www.baidu.com"
data = urllib.request.urlopen(url).read()
data = data.decode('UTF-8')
print(data)
这是 urllib 最简单的使用方法,我们通过 urlopen 方法读取一个 URL,并调用 read 方法获取我们刚刚说到的 HTML 内存字符串,打印出来就是一堆标签格式的网页字符串了。
urlopen函数返回了一个HTTPResponse对象,这个对象挺有用的,是爬取请求的返回对象,我们可以通过它查看爬取 URL 请求的状态,还有一些对象信息等,比如 getcode 为 200 代表了网络请求成功。
>>> a = urllib.request.urlopen(full_url)
>>> a.geturl()
'http://www.baidu.com/s?word=Jecvay'
>>> type(a)
<class 'http.client.HTTPResponse'>
>>> a.info()
<http.client.HTTPMessage object at 0x03272250>
>>> a.getcode()
200
当然了,我们的网络请求的实际情况是比较复杂的,有时候你需要在请求中添加数据,或者更改一下 header,urllib 的 Request 对象可以满足这些需求。
import urllib.request
request = urllib.request.Request("http://www.baidu.com")
# 添加请求头
request.add_header('User-Agent', 'Mozilla/5.0')
# 添加数据
request.data = b"I am a data"
response = urllib.request.urlopen(request)
还有一些更加特殊的场景,比如有的网页需要 Cookie 处理,有的网页需要添加网页代理才能访问,有的网页需要输入账号密码验证,有的网页需要 HTTPS 协议才能访问。面对这些比较复杂的场景,urllib 提供了 Handler 这个强大的工具来处理这些需求。
不同的场景有不同的 Handler,比如处理 Cookie 使用 HTTPCookieProcessor ,处理网络代理可以使用 ProxyHandler,使用的时候,我们用 Handler 来构建一个 opener,然后用把 opener 安装到 request 上,这样再进行请求的时候,所安装的 Handler 就会起到处理特殊场景的作用。我们拿输入用户密码这种场景来举例:
import urllib.request
# 构建基础HTTP账号验证Handler
auth_handler = urllib.request.HTTPBasicAuthHandler()
auth_handler.add_password(realm='PDQ Application',
uri='https://mahler:8092/site-updates.py',
user='klem',
passwd='kadidd!ehopper')
# Handler 构建 opener
opener = urllib.request.build_opener(auth_handler)
# 安装 opener
urllib.request.install_opener(opener)
urllib.request.urlopen('http://www.example.com/login.html')
我们利用多态构建了一个 HTTP 基本验证信息的 Handler,添加好相关的账号密码信息后,构建了一个 opener,并把 opener 安装到 request 上,在请求一个带有验证地址的时候,将会填充我们在 Handler 中填写的数据。
有关 urllib 的 API 大家可以参考 Python3 官方文档,文档写的清晰明了而且有官方的代码示例,我也阅读过文档,感觉 Python 官方的文档确实非常用心,很舒服。requests 这个第三方库大家可以自行了解下,刚开始学习的时候,实际上 urllib 基本能满足你的需求了。
网页解析器
网页下载器将网页下载到本地后,我们需要使用网页解析器从下载好的本地文件或者内存字符串中提取出我们需要的有价值信息。对于定向爬虫来说,我们需要从网页中提取两个数据,一个是我们需要的价值数据,另外就是该网页 URL 所能跳转的 URL 列表,这个列表我们将输入到 URL 管理器中进行处理。Python 中有以下几种方式可以实现网页解析器。
一个就是使用正则表达式,这个方式最为直观,我们将网页字符串通过正则的模糊匹配的方式,提取出我们需要的价值数据,这种方法虽然比较直观,但如果网页复杂,会比较麻烦。但我的建议,大家还是学一下正则表达式,这个毕竟是一门扛把子技术了,至少你得看得懂。
同时推荐大家另一款分析语言 XPATH,它是一门高效的分析语言,语法表达相比正则来说清晰简单,如果你掌握的好,基本可以替代正则,大家有兴趣可以搜索学习一下哦~
Python 还可以使用 html.parser,lxml,以及第三方库 BeautifulSoup 来进行网页解析。BeautifulSoup 本身包含了 html.parser 和 lxml,功能较为强大,它使用结构化解析来解析网页,结构化解析就是使用 DOM 树的方式进行上下级元素的遍历访问,从而达到解析和访问 HTML 文件的目的。一图理解
我们这里介绍下 BeautifulSoup,安装很简单,有好几种方法,最简单的方法就是使用命令 pip install beautifulsoup4 来安装,一些安装的流程和一些问题就不在这里多说了。
介绍下 BeautifulSoup 的使用方法,更加详细的 API 还是要看官方文档,而且 BS 的文档有友好的国人开发者在进行翻译,还是非常不错的~
使用 BS 的流程是,首先创建 BS 对象,传入对应的网页字符串,并指定相应的解析器(html.parser 或者 lxml),然后使用 find_all 或者 find 函数来进行搜索节点,最后通过获取到的节点访问对应的名称、属性或者文字,从而得到你想要的信息。
举个例子,现在有这样一个网页字符串信息:
<a href='123.html' class='article_link'> python </a>
在这段字符串里,节点的名称是 a,节点属性有 href='123.html' 和 class='article_link' 这两个,节点内容是 python。
有了这三个节点信息,我们就可以开始进行代码的编写了
from bs4 import BeautifulSoup
# 根据 HTML 网页字符串创建 BS 对象
soup = BeautifulSoup(
html_doc, # HTML 字符串
'html.parser', # HTML 解析器
from_encoding='utf8')# HTML 编码
# 查找所有标签为a的节点
soup.find_all('a')
# 查找所有便签为a,链接符合/view/123.htm形式的节点
soup.find_all('a',href='/view/123.htm')
# 查找所有标签为div,class为abc,文字为Python的节点
soup.find_all('div',class_='abc',string='Python')
# 使用正则表达式匹配
soup.find_all('a',href=re.compile(r'/view/\d+\.htm))
find_all 和 find 使用方法一致,只是 find_all 返回的是一个节点列表。注意到,find 方法是可以使用正则表达式进行模糊匹配的,这是它强大的地方,获取到节点 node,我们就可以很容易的获取到节点信息了。
# 得到节点:<a href='1.html'>Python</a>
# 获取节点标签名称
node.name
# 获取节点的href属性
node['href']
# 获取节点文字
node.get_text()