用Python写网络爬虫-学习总结

关注公众号“码农帮派”,查看更多系列技术文章:

 

一.关于爬虫的一些零散知识

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)

 

 

 

 

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值