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: /
以上是豆瓣网站的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,参见