关注公众号“码农帮派”,查看更多系列技术文章:
一.关于爬虫的一些零散知识
1.Robots协议
大多数网站的主页下会有robots.txt文件,标识了爬虫爬取该网站信息时,哪些资源是有限制的,可以使用Python的标准库robotparser来检测将要爬取的url链接是否被允许:
# coding=utf-8
import robotparser
# 实例话一个Robots协议检测对象
rp = robotparser.RobotFileParser()
# 设置robots协议文件的路径
rp.set_url("http://baike.baidu.com/robots.txt")
rp.read()
url_1 = "http://baike.baidu.com/client/12dhy40"
user_agent_1 = "Baiduspider"
# 打印访问权限
print rp.can_fetch(user_agent_1, url_1)
url_2 = "http://baike.baidu.com/item/Python"
user_agent_2 = "Baiduspider"
# 打印访问权限
print rp.can_fetch(useragent=user_agent_2, url=url_2)
打印结果:
/usr/bin/python2.7 /home/mxd/文档/WorkPlace/python/PythonStudy/test/test.py
False
True
Process finished with exit code 0
2.识别网站使用的技术
利用builtwith模块可以检测出网站使用的技术信息:
# coding=utf-8
import builtwith
# 打印网站使用技术的信息
print builtwith.parse('http://www.imooc.com/')
打印结果:
/usr/bin/python2.7 /home/mxd/文档/WorkPlace/python/PythonStudy/test/test.py
{u'javascript-frameworks': [u'React', u'AngularJS', u'jQuery', u'Vue.js'], u'web-servers': [u'Nginx']}
Process finished with exit code 0
3.查看网站所有者的信息
WHOIS协议可以查询到域名注册者的信息,Python中针对该协议的模块为whois:
# coding=utf-8
import whois
# 打印网站所有者的信息
print whois.whois('http://www.imooc.com/')
打印结果:
{u'updated_date': [datetime.datetime(2016, 2, 5, 0, 0),
datetime.datetime(2016, 2, 5, 4, 19, 20)],
u'status': [u'ok https://icann.org/epp#ok',
u'ok http://www.icann.org/epp#OK'],
u'name': u'zhang yu',
u'dnssec': u'unsigned',
u'city': u'beijingshi',
u'expiration_date': [datetime.datetime(2018, 10, 6, 0, 0),
datetime.datetime(2018, 10, 6, 2, 57, 51)],
u'zipcode': u'100120',
u'domain_name': [u'IMOOC.COM', u'imooc.com'],
u'country': u'CN',
u'whois_server': u'grs-whois.hichina.com',
u'state': u'bei jing',
u'registrar': u'HICHINA ZHICHENG TECHNOLOGY LTD.',
u'referral_url': u'http://www.net.cn',
u'address': u'beijingshi,,',
u'name_servers': [u'NS3.DNSV3.COM',
u'NS4.DNSV3.COM',
u'ns3.dnsv3.com',
u'ns4.dnsv3.com'],
u'org': u'open',
u'creation_date': [datetime.datetime(2012, 10, 6, 0, 0),
datetime.datetime(2012, 10, 6, 2, 57, 51)],
u'emails': [u'zhangy@mail.open.com.cn',
u'yunwei_system@mail.open.com.cn',
u'abuse@list.alibaba-inc.com']}
二.下载网页
使用urllib2模块进行网页的下载,在上一篇博客中,拉取百度百科的词条获得词条对应的url,但url可能已经过期,我们再去拉取会报异常,所以需要使用try-except捕获异常:
try:
html_doc = urllib2.urlopen(url)
except urllib2.URLError as e:
# 异常情况,打印异常原因和状态码
print e.reason, e.code
使用urllib2下载网页的时候,可能会出现异常,其中code为4xx为请求异常,5xx为服务器错误,当URLError的code为5xx时,可以考虑重新发起请求:
# 下载网页的函数#
# num_retries为遇到异常重新发起网络请求的次数,默认为2次
def downloadHtml(url, num_retries = 2):
try:
html_doc = urllib2.urlopen(url)
except urllib2.URLError as e:
html_doc = None
if num_retries > 0:
if hasattr(e, 'code') and 500 <= e.code < 600:
# URLError.code为5xx可以重新请求
return downloadHtml(url, num_retries - 1)
return html_doc
(2-1)ID遍历爬虫
很多网站由于数据挺多,会采用page切换的方式展现数据,类似于:
http://www......../page=1
http://www......../page=2
这样的。可以使用循环,自动爬取每个page对应的数据。
【模块简介-itertools】
itertools模块模块用于生成各种循环器。
|- itertools.count(start, step) : 从start开始,每隔step生成一个数字,直到无穷大
无限循环器 |- itertools.cycle(seq):无限次的循环seq中的每一个item
|- itertools.repeat(item):无限循环输出item
因为我们并不知道page的最后一个数是多少,因此可以使用itertools进行无限次向后递进循环:
import itertools
# itertools.count(1)可以从1开始无限次向后循环,1,2,3,4
for page in itertools.count(1):
# 生成不同page的url链接
url = "http://www.xxxxx.com/page={0}".format(page)
html_doc = downloadHtml(url)
if html_doc is None:
break
else:
# 处理html_doc....
上面的代码中,当遇到某一page对应的url下载到的html_doc为None时,就认为已经到最后一页了,即停止继续爬取网页,但有些情况下,html_doc可能是因为某一page对应的网页失效,或其他原因而导致下载失败,但其后面的page对应url的网页正常,那么上面的代码就有问题了,需要进一步改进:
import itertools
# 设定最大出错,即html_doc为空的最大次数
max_errors = 5
num_errors = 0 # 当前出错的次数
for page in itertools.count(1):
# 生成不同page的url链接
url = "http://www.xxxxx.com/page={0}".format(page)
html_doc = downloadHtml(url)
if html_doc is None:
num_errors += 1
if num_errors >= max_errors:
# 当连续5个page下载错误,即停止继续爬取
break
else:
num_errors = 0 # 出错次数置为0
# 处理html_doc....
(2-2)链接爬虫
“百度百科”中爬取词条的时候,在每个词条网页中会包含相关的词条,我们可以使用爬虫对当前网页中其他词条信息进行抓取,这样就可以爬取得到大量的词条信息,但是我们会发现,爬取到的词条的url链接如下①:
① |- /view/76320.htm 相对链接
② |- http://baike.baidu.com/view/76320.htm 绝对链接
而完整的url如②,①为相对链接,它不包括协议和服务器部分,②为绝对链接,对于浏览器来说,绝对/相对链接都可以被识别,但对于urllib2来说,只能识别绝对链接,因为要将相对链接拼接成为绝对链接,此时可以使用urlparse模块进行拼接,将相对链接url_relative拼接为绝对链接url_full:
import urlparse
# 相对链接 url_relative
# 绝对链接 url_full
# url_templet 为模板url,也是一个绝对链接
url_full = urlparse.urljoin(url_templet, url_relative)
(2-3)支持代理
有些url的访问需要一个代理IP:
import urllib2
# 使用proxy代理访问url
proxy = "http://www.proxyURL" # 代理的链接
opener = urllib2.build_opener()
proxy_params = {urllib2.urlparse(url).scheme: proxy}
opener.add_handler(urllib2.ProxyHandler(proxy_params))
# request为urllib2.Request()
response = opener.open(request)
由此完整的downloadHtml()函数可以写成如下的方式:
import urllib2
def downloadHtml(url, user_agent="Mollia/5.0", proxy=None, num_retries=2):
header = {"User-Agent": user_agent}
# 创建一个Request对象
request = urllib2.Request(url, headers=header)
opener = urllib2.build_opener()
if proxy: # 如果有代理
proxy_params = {urllib2.urlparse(url).scheme: proxy}
opener.add_handler(urllib2.ProxyHandler(proxy_params))
try:
html_doc = opener.open(request).read()
except urllib2.URLError as e:
html_doc = None
if num_retries > 0:
if hasattr(e, 'code') and 500 <= e.code < 600:
return downloadHtml(url, user_agent, proxy, num_retries - 1)
return html_doc
(2-4)下载限速
有些网站访问对访问速度进行了限制,为了不让爬虫被禁止,需要对爬虫下载网页的速度进行一定的限制:
import urlparse
import datetime, time
class Throttle:
def __init__(self, delay):
self.delay = delay # 需要延迟的时间
# 字典用来存储某个域名访问的时间
self.domains = {}
def wait(self, url):
# 从url中获取域名部分
domain = urlparse.urlparse(url).netloc
# 从self.domains中获取到domain对应的上次访问的时间
# 此处使用字典的get()方法获取,可以避免因为key不存在而引起的异常
last_accessed = self.domains.get(domain)
if self.delay > 0 and last_accessed is not None:
# 计算得到应该sleep的时间
sleep_seconds = self.delay - (datetime.datetime.now() - last_accessed).seconds
if sleep_seconds > 0:
# 进行sleep
time.sleep(sleep_seconds)
# 更新/增加该域名domain对应的访问时间信息
self.domains[domain] = datetime.datetime.now()
上面的类,使用了一个字典,来存储每个域名最近一次访问的时间,每次访问一个域名的url的时候,比对上次访问时间,要是没有超过延时delay,则进行相应时间的sleep操作,否则继续访问,有了上面的类,我们就可以实现下载限速的目的:
# 设置下载限速的时间
# 即一个域名每次访问的时间间隔最小为3s
delay = 3
thrittle = Throttle(delay)
# ..... 获取要访问的url ......
thrittle.wait(url) # 针对url进行wait()过程
# 进行网页下载
html_doc = downloadHtml(url, user_agent, proxy, num_retries)