网络爬虫简介
爬虫什么时候有用
正常情况下,每个网站应该提供API,以结构化的数据格式分享,然后虽然有些网站已经提供了类似的API,但是它们通常会限制可以抓取到的数据以及数据访问的频率。我们不能仅仅依赖于API去访问我们需要的在线数据,而是应该学习一些爬虫技术的相关知识。
爬虫是否合法
抓取某个网站的数据时,应该约束自己的抓取行为,否则他们可能会被封禁IP,甚至采取更进一步的法律行动。这就要求下载请求的速度需要限定在一个合理值的范围内,并且还需要设定一个专属的用户代理来标识自己。
背景调研
在深入讨论爬取一个网站前,我们首先需要对目标站点的规模和结构进行一定程度的了解。网站自身的robots.txt和Sitemap文件都可以为我们提供一定的帮助,此外还可以借助搜索引擎。
- 检查robots.txt
很多网站会定义robots.txt文件,让爬虫了解爬取网站时存在哪些限制。检查robots.txt文件可以最小化爬虫被封禁的可能,而且还能发现和网站结构相关的线索。robots.txt文件通常放置在网站根目录下,当使用爬虫访问一个网站时,首先会检查该网站中是否存在robots.txt文件,如果找到了,根据这个文件内容来确定它的访问权限的范围。
- 检查网站地图
网站提供的Sitemap文件可以帮助爬虫定位网站最新的内容,而无需爬取每一个网页。我们无法依靠Sitemap文件提供每个网页的链接。https://www.xml-sitemaps.com/
网站可以搜索网站中部分的网页。
- 估算网站的大小
目标网站的大小会影响我们如何进行爬取。估算网站大小的简单方法是检查百度爬虫的结果,百度可能已经爬取过我们感兴趣的网站。我们可以通过百度搜索的site关键字过滤域名结果,从而获取该信息。site:www.baidu.com
,在域名后面添加URL可以对结果进行过滤,仅显示网站的某些部分。
- 识别网站所用技术
builtwith模块可以检查网站构建的技术类型。使用pip install builtwith
安装。
import builtwith
builtwith.parse('http://www.baidu.com')
- 寻找网站所有者
对于一些网站,我们可能关心所有者是谁,可以使用WHOIS协议查询域名的注册者是谁,Python中有个针对该协议的封装库,使用pip install python-whois
安装。
import whois
domain = whois.whois('baidu.com')
print(domain)
编写第一个爬虫
爬取就是下载包含有感兴趣数据的网页,爬取的方式有很多种,取决于目标网站的结构。爬取网站常见的方法:爬取网站地图、遍历每个网页的数据库ID、跟踪网页链接。
使用urllib模块下载URL,传入URL参数时,该函数会下载网页并返回其Html,下载错误时捕获异常。下载时遇到的错误经常是临时性的,比如服务器过载时返回的503服务不可用错误。对于此类错误,我们可以尝试重新下载,因为这个服务器问题下载可能已解决。4xx错误发生在请求存在问题时,而5xx错误发生在服务器端存在问题时。为了下载更加可靠,我们需要控制用户代理的设定。urlparse模块可以将相对链接转换成绝对链接的形式,方便定位网页的所有细节。避免下载禁止爬取的URL,需要安装pip install robotparser
模块解析robots.txt文件。有时我们徐璈使用代理访问某个网站,可以使用requests模块来实现。如果我们爬取网站速度过快,就会面临被封禁或是服务器过载的风险,可以在两次下载之间添加延时对爬虫限速。避免陷入爬虫陷阱,简单方法时记录到达当前网页经过了多少个链接,当到达最大深度时,爬虫就不再向队列中添加该网页中的链接了。
# urllib库是用来处理网络请求的python标准库,urllib.request请求模块用于发起网络请求,urllib.error异常处理模块用于处理请求异常,urllib.parse解析模块用于解析URL、合并、编解码,urllib.robotparser robots.tx用于解析robots.txt文件
# 在python3中urllib2模块被合并到了urllib中,叫做urllib.request和urllib.error
# urllib3不是标准库,需要使用pip安装,提供了线程安全池和文件post等
from urllib import request, error
def download(url, user_agent='wswp', num_retries = 2):
print('Downloading...', url)
headers = {'User-agent': user_agent}
req = request(url, headers=headers)
try:
html = request.urlopen(req).read()
except error as e:
print('Downloading error:', e.reason)
html = None
if num_retries > 0:
# 重试5xx错误
if hasattr(e, 'code') and 500 <= e.code < 600:
return download(url, num_retries - 1)
return html