一.关于爬虫的一些零散知识
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'[email protected]', u'[email protected]', u'[email protected]']}
二.下载网页
使用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()
# 设置下载限速的时间 # 即一个域名每次访问的时间间隔最小为3s delay = 3 thrittle = Throttle(delay) # ..... 获取要访问的url ...... thrittle.wait(url) # 针对url进行wait()过程 # 进行网页下载 html_doc = downloadHtml(url, user_agent, proxy, num_retries)