python爬虫系列序

关于爬虫的了解,始于看到这篇分析从数据角度解析福州美食,和上份工作中的短暂参与。

长长短短持续近一年的时间,对其态度越来越明晰,噢原来这就是我想从事的工作。

于是想要系统学习的心理便弥散开来……

参考书籍:《利用python写网络爬虫

 

爬虫简介

  互联网包含了迄今为止最多的数据集,我想这句话没有问题。它们以嵌入的方式呈现在网站的结构和样式当中,供我们公开访问大部分时候。但是这些数据又难以复用,所以必须得抽取出来使用,从网页中抽取数据的过程便称为网络爬虫。

 

爬虫调研

  首先,我们得认识到爬取一个网站的数据时,自己是一个访客,应当约束自己的抓取行为,限速和数据商用等。其次,在深入爬取之前,我们需要对目标网站的规模和结构进行一定了解。

1.检查robot.txt

这个文件定义了爬取网站存在的限制,良好的网络公民都应该遵守这些限制,这也是避免爬虫被封禁的有效途径之一

    
 1 #section1
 2 User-agent: *
 3 Disallow: /subject_search
 4 Disallow: /amazon_search
 5 Disallow: /search
 6 Disallow: /group/search
 7 Disallow: /event/search
 8 Disallow: /celebrities/search
 9 Disallow: /location/drama/search
10 Disallow: /forum/
11 Disallow: /new_subject
12 Disallow: /service/iframe
13 Disallow: /j/
14 Disallow: /link2/
15 Disallow: /recommend/
16 Disallow: /trailer/
17 Disallow: /doubanapp/card
18 Sitemap: https://www.douban.com/sitemap_index.xml
19 Sitemap: https://www.douban.com/sitemap_updated_index.xml
20 # Crawl-delay: 5
21 
22 #section2
23 User-agent: Wandoujia Spider
24 Disallow: /
View Code

以上是豆瓣网站的robot.txt文件的内容,section1规定,不论哪种用户代理,两次下载请求之间应延迟5秒抓取,避免服务器过载;还列出了一些不允许爬取的链接,以及定义了sitemap文件。section2规定,禁止用户代理为Wandoujia的爬虫爬取该网站

2.检查网站地图

网站提供的Sitemap文件,即网站地图,可以帮助爬虫定位网站最新的内容,避免爬取每一个网页,该文件比较大,都采取压缩的方式展示。

3.识别网站所用技

构建网站的技术会影响如何爬取,python的builtwith库可以帮助了解这一点。

1 >>> import builtwith
2 >>> builtwith.parse("https://www.douban.com")
3 {u'javascript-frameworks': [u'jQuery'], u'tag-managers': [u'Google Tag Manager'], u'analytics': [u'Piwik']}

4.估算网站大小

目标网站的大小会影响我们爬取的方式,比较小的网站,效率就没有那么重要,而上百千万级别的网页站点,则需要考虑分布式下载方式,不然串行下载,可能需要持续好久才能完成。

 

爬虫准备

1.清楚爬取的目的。爬取的过程大致分为:

1)爬取:下载网页(requests,urllib,urllib2),解析网页(re,lxml,BeautifulSoup,选其一)

2)存储:MongoDB,文件

3)分析:Pandas

4)展示:Matplotlib , Pyechart

 

2.爬虫需要熟悉或安装的第三方库

1)请求和响应:requests,urllib,urllib2

注:在python2.7语法里,urllib和urllib2是两个不同的内置模块,python3合二为一了。两者的区别在于urllib2可接收一个Request对象,并以此可以设置一个headers,但urllib只能接收url;urllib可提供进行urlencode方法,urllib2不具有

2)提取数据:re,lxml,BeauitifulSoup

3)数据格式:json,xml

4)数据库连接:pymongo

5)爬虫框架:Scrapy

6)数据分析展示:pandas,matplotlib,pyechart

 

3.爬虫工具准备

1)python2.7:自带IDEL编辑器,使用pip包管理工具,安装第三方库。

常见命令有:pip list    #查看所有已安装的

      pip install 第三方库名  #安装

      pip uninstall 第三方库名  #卸载

      pip install --upgrade 第三方库名  #升级

 2)Anaconda2:可以看做时Python的一个集成安装,安装它后就默认安装了python、IPython、集成开发环境Spyder和众多的包和模块。非常方便,注意是非常方便。使用conda包管理工具,安装第三方库。常用命令和pip类似

Anaconda 内置包更新 eg:conda update pandas

或者源码安装:

进入包路径,输入 python setup.py install

注:人生苦短,使用Anaconda。自带numpy,pandas,matplotlib第三方库,用来数据分析,不用考虑各个库之间的依赖关系非常赞。

 

爬虫初识

通常为了抓取网站感兴趣的内容,首先需要下载包含该数据的网页,这过程一般称为爬取(crawling)。

import requests
import urllib2

def download1(url):
    "简单下载"
    print 'downloaing1:',url
    html = requests.get(url).content
    return html

def download2(url):
    "新增捕获错误的下载"
    print 'downloaing2:',url
    try:
        html = requests.get(url).content
    except urllib2.URLError as e:
        print 'downing error',e.reason
        html = None
    return html

def download3(url,num_reties = 3):
    "新增重试次数的下载"
    print 'downloading3:',url
    try:
        html = requests.get(url).content
    except urllib2.URLError as e:
        print 'downing error',e.reason
        html = None
        if num_reties > 0:
            if hasattr(e,'code') and 500 <= e <= 600:
                html = download3(url,num_reties -1)
    return html

def download4(url,num_reties = 3):
    "新增请求头的下载"
    print 'downloading4:',url
    try:
        headers = {'User-agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36'}
        html = requests.get(url,headers = headers).content
    except urllib2.URLError as e:
        print 'downing error',e.reason
        html = None
        if num_reties > 0:
            if hasattr(e,'code') and 500 <= e <= 600:
                html = download3(url,num_reties -1)
    return html

def download5(url,num_reties = 3):
    "新增代理的下载"
    print 'downloading5:',url
    try:
        headers = {'User-agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36'}
        proxy = {"http": "dev-proxy.oa.com:8080","https": "dev-proxy.oa.com:8080"}
        html = requests.get(url,headers = headers,proxies = proxy).content
    except urllib2.URLError as e:
        print 'downing error',e.reason
        html = None
        if num_reties > 0:
            if hasattr(e,'code') and 500 <= e <= 600:
                html = download3(url,num_reties -1)
    return html
    
#download = download1
#download = download2
#download = download3
#download = download4
download = download5

if __name__ == '__main__':
    print download('http://so.gushiwen.org/type.aspx')

注:download = download5 会报ProxyError的错,因为代理ip是随便写的

 

从每个网页中抽取一些数据的过程叫抓取(scraping),根据网页结构特点(html/js/ajax),我们一般会用正则表达式、Beautiful Soup和lxml这三种方法抓取数据。

1.正则表达式,详情参见

import re

s = """
<div id="contson52821" class="contson">
<p>
寻寻觅觅,冷冷清清,凄凄惨惨戚戚。乍暖还寒时候,最难将息。三杯两盏淡酒,怎敌他、晚来风急?雁过也,正伤心,却是旧时相识。
<br>
满地黄花堆积。憔悴损,如今有谁堪摘?守着窗儿,独自怎生得黑?梧桐更兼细雨,到黄昏、点点滴滴。这次第,怎一个愁字了得!(守着窗儿 一作:守著窗儿)
</p>
</div>
"""
SFiltered = re.sub(r'\<.*?\>'," ",s).strip()
print SFiltered  

2.Beautiful Soup

from bs4 import BeautifulSoup

s = """
<div id="contson52821" class="contson">
<p>
寻寻觅觅,冷冷清清,凄凄惨惨戚戚。乍暖还寒时候,最难将息。三杯两盏淡酒,怎敌他、晚来风急?雁过也,正伤心,却是旧时相识。
<br>
满地黄花堆积。憔悴损,如今有谁堪摘?守着窗儿,独自怎生得黑?梧桐更兼细雨,到黄昏、点点滴滴。这次第,怎一个愁字了得!(守着窗儿 一作:守著窗儿)
</p>
</div>
"""
soup = BeautifulSoup(s,'lxml')
#soup.find_all('p')
for i in soup.find_all('p'):
    print i.text

3.lxml,详情参见

from lxml import etree

s = """
<div id="contson52821" class="contson">
<p>
寻寻觅觅,冷冷清清,凄凄惨惨戚戚。乍暖还寒时候,最难将息。三杯两盏淡酒,怎敌他、晚来风急?雁过也,正伤心,却是旧时相识。
<br>
满地黄花堆积。憔悴损,如今有谁堪摘?守着窗儿,独自怎生得黑?梧桐更兼细雨,到黄昏、点点滴滴。这次第,怎一个愁字了得!(守着窗儿 一作:守著窗儿)
</p>
</div>
"""
selector = etree.HTML(s)
s1 = selector.xpath('//*[@id="contson52821"]/p/text()')
#print s1
for i in s1:
    print i

输出:三者都一样

三种抓取方法比较

抓取方法 性能 使用难度
正则表达式 快,C编写
Beautiful Soup 慢,Python编写 简单
lxml 快,C编写 相对简单

总结:如果爬虫瓶颈不是抓取数据,使用较慢的方法也不是问题,如果只抓取少量数据避免额外包依赖,也许使用正则表达式更方便,通常情况下,lxml抓取比较好,正则和Beautiful Soup 在特定场景下使用。

 

爬虫优化

1.下载限速:Throttle类记录了每个域名上次访问的时间,如果当前时间距离上次访问时间小于指定延迟,则执行睡眠操作。

class Throttle:
    def __init__(self, delay):
        self.delay = delay
        self.domains = {}  #记录一个域名最后一次访问的时间戳
        
    def wait(self, url):
        domain = urlparse.urlparse(url).netloc
        last_accessed = self.domains.get(domain)

        if self.delay > 0 and last_accessed is not None:
            sleep_secs = self.delay - (datetime.now() - last_accessed).seconds
            if sleep_secs > 0:
                time.sleep(sleep_secs)
        self.domains[domain] = datetime.now() #更新最后一次访问的时间戳

2.多次抓取,添加缓存机制

class MongoCache:
    def __init__(self,client = None,expires = timedelta(days = 30)):
        self client = MongoClient('localhost',27017)        #连接默认端口
            if client is None else client
        self.db =client.cache
        self.db.webpage.create_index('timestamp',expiresAfterSecond = expires.total_seconds())    #创建过期缓存页面索引
    
    def __getitem__(self,url):
        record = self.db.webpage.find_one({'_id':url})
        if record:
            return record['result']
        else:
            raise KeyError(url + 'does not exist')
        
    def __setitem__(self,url,result):
        record = {'result':result,'timestamp':datatime.utcnow()}
        self.db.webpage.updata({'_id':url,{'$set',record},upsert = True})

说明:getitem和setitem为python的魔法方法,更多参见

3.并发下载多进程

import requests
from multiprocessing import Pool
import time

urllist = ['http://so.gushiwen.org/type.aspx?p={}&c=%e5%94%90%e4%bb%a3'.format(i) for i in range(1,501)]
def download(url):
    for url in urllist:
        print url
        html = requests.get(url).content
        return html
    
if __name__ == "__main__":  
    #创建多个进程,并行执行
    T1 = time.time()
    pool = Pool(processes = 5)
    new_lst = pool.map(download,urlist)
    pool.close()   #关闭进程池,不再接受新的进程
    pool.join()   #主进程阻塞等待子进程的退出
    T2 = time.time()
    print u'并行执行时间:',int(T2-T1)

 

更多爬虫实例参见如下:

爬虫实例(一):每日一文爬虫

爬虫实例(二):唐诗宋词爬虫

爬虫实例(三):中国日报爬虫

爬虫实例(四):天猫商品评论爬虫

爬虫实例(五):豆瓣电影爬虫

爬虫实例(六):今日头条爬虫

爬虫实例(七):饿了么爬虫

关于爬虫框架Scrapy,参见

posted on 2017-03-05 20:08  Ryana  阅读(453)  评论(0编辑  收藏  举报