用了一段时间的scrapy了,比直接Requests、Urllib确实是好用很多,框架还是不错的,偶然看到这篇帖子,确实是深有体会,copy下作为记录
在编程语言的世界里,python似乎被贴上了做爬虫的一个标签,强而有力。而scrapy做为另一个老牌的开源项目,更是大规模抓取不可或缺的一个重要力量。纵使scrapy依旧有一些长期无法解决的诟病,但是他在抓取过程帮程序员解决的一系列的细节问题,还是有无以伦比的优势。
缺点
1. 重量级
scrapy依赖于twisted,而twisted是python世界的出名的重量级框架。要读懂scrapy的源码,必须先了解twisted(deffered,reactor)的工作原理,读懂twisted的源码,这样一切看起都非常困难。
2. 内存
为了防止重复对同一个url抓取,避免无限递归的遍历url,scrapy把所有处理过的request(包括method,url,headers)放到内存当中,如果spider处理的request够多的话,spider占用的内存是惊人的。
解决方法
必要的时,将一个大型spider(可预计抓取的url过多)拆分成多个spider。
bloom,以空间换取准确性的一种去重算法。settings.py里配置DUPEFILTER_CLASS = ‘scrapy.dupefilter.RFPDupeFilter’,用自己实现的bloom算法覆盖即可。要注意的是,bloom是牺牲了准确性开换取空间开支小的算法,在内存能够胜任,就显得多此一举。
3. xpath的容错(并不是scrapy独有)
经常会遇到解析一些非标准的html,在浏览器会对他们进行容错处理,而不会影响到页面展现。但是用xpath就要了命了,而且通常还不容易察觉。解决办法
将整个html转化成标准的xml,在用xpath解析。比如
xx,这个时候就要想办法把第二个去掉。用re解析抽取结构化信息。不推荐用re,不是因为在这种场景下效果不好,而是因为用正则会增加spider的维护成本,具体的下面会提到。
优点
理论只是实践基础和指导原则,要知道理论到实践还是有很多路要走的。本人比较反对将重复造轮子的东西轻易的拿到生产上去实践。spider不仅仅是把内容结构化,而是在各个细节处理上都很全面。
1. 智能编码
scrapy会从meta中自动提取里涉及到的编码,如果没有则尝试gb2312,utf-8编码,最后还不行的话,就用自定义的编码DEFAULT_RESPONSE_ENCODING。虽然这种方式不能完成正确,也能保证90%(我实践中得到的结果)以上的正确性。
2. 灵活的pipeline
pipeline以管道的方式处理item,比如说item再加工,过滤,持久化都可以再这里处理。可以定义多个pipeline,对item做不同的处理。如果在item中有个image字段,可以先用MediaPipeline处理过,将图片下载到本地,再讲item插入到数据库。
3. 强大的xpath
scrapy是xpath作为解析工具,之前提到的也可以用正则但是不推荐,一个很重要的原因是,xpath维护,可读性要比正则强太多。spider的维护一项持久而耗时的工作,特别是一些静态网站,都是通过cms系统发布,这样会导致网站的结构,样式调整的比较频繁,用re会陷入无尽的烦恼当中。而xpath固然也会有种问题,但是良好的可读性,会让维护成本成倍的降低。
4. 处理http请求的各种细节
设置抓取时间间隔,在spider中设置download_delay=x(单位是秒)
配置代理,settings中增加HttpProxyMiddleware(默认),设置系统代理
配置自定义代理
1 import base64
2
3 class ProxyMiddleware(object):
4 def process_request(self, request, spider):
5 request.meta['proxy'] = "http://YOUR_PROXY_IP:PORT"
6 proxy_user_pass = "USERNAME:PASSWORD"
7 encoded_user_pass = base64.encodestring(proxy_user_pass)
8 request.headers['Proxy-Authorization'] = 'Basic ' + encoded_user_pass
也可以将代理配置成随机的,只需要在上面代码中稍作处理。
http code的处理,正常情况下,scrapy的response只处理20x,设置handle_httpstatus_list = [301,302,204,206,404,500]
retry机制,由于网络或者对方服务器的原因,对url重复处理是非常有必有,spider中设置RETRY_TIMES,RETRY_HTTP_CODES
模拟浏览器行为,设置user-agent
设置默认headers, DEFAULT_REQUEST_HEADERS
cookies处理,开启COOKIES_DEBUG=True后,可以再Request中带上cookies
yield Request(url=’http://www.douban.com‘,cookies={‘session’:’xxxx’})
等等吧,太多了。
5. 对https,ftp等协议支持,使用过程和http一样。
6. 强大的监控,日志系统
默认开启TELNETCONSOLE_ENABLED = 1,WEBCONSOLE_ENABLED = 1具体的可以看文档。一般的情况下,会查看spider close的日志,整个spider的运行状态都查看到
**重点内容**7. 支持json,jsonlines,csv,xml,marshal,pickle导出
下面接着写:
体会
断断续续的使用scrapy已经很长时间,在各种问题也算是有所领悟(所需工具firefox,firebug,firefox的xpath插件)。
1. 非标准的html解析问题(上一篇提到过)
这个问题蛋疼不是问题有多难解决,而是很难定位到错误的原因。在firefox中确保xpath没有写错的情况下,找不到相应的数据,这个时候就要考虑是否是这个原因,直接查看页面源码,如果有不标准的html,在firefox中会有红色标识。
selector = XPathSelector(text=response.body.replace('<div class="left-clear"/>','<div class="left-clear">'))
2. 反扒
通常的情况下,一些流量比较大的网站都有反爬虫的机制,避免恶意的访问,减轻服务器的压力。一般的情况的下调整抓取的间隔,更换代理。2种方式都有缺点。
抓取间隔设置download_delay=1,通常设置这个属性,整个spider的性能变得非常低。
更换代理,可以到http://pachong.org/去找免费的代理,一般速度也不快,而且不稳定。
3. http code欺骗(我自己取得名字)
一般情况数据的正常返回都是20x,scrapy会自动忽略掉50x的http code。这个(http://www.travelplus.cn/plus/list.php?tid=8
)用浏览器打开的时候,你看不出任何问题。用firebug就能清晰的看到,其实它是将500页面,当做正常页面显示。
在spider里添加handle_httpsatstus_code = [500],一切照旧即可。
4. 模拟浏览器请模拟彻底。
如果你有服务器点开发的经验,就不难理解,服务器可以通过任何一个headers或者参数来屏蔽你的request。参数自然不用说,能带上都带上,至于一些参数加密,以后再提。主要是针对headers中的几个常用的。
Cookie,这个最常见用的,平心而论scrapy对cookie的支持只是基础的支持,用起来不太好用。
User-Agent, 这个主要还是服务器为了区分request是来自pc端还是手机端,会导致response不一样。
Referer, 防止伪造的跨网站请求。
X-Requested-With=XMLHttpRequest, 这个很好理解,对于同一个url,ajax request和普通request的response结果不一样很正常
Content-Length, 一些网站没有这个header会返回411
以上5个header基本上能解决问题,万一遇到顽固份子,那只好彻底的模仿吧。
5. 不要忽略firebug里监控到任何http request
部分网站在提交数据的时候,会先跳转到一个页面,这个页面是空白的页,只是包含一些隐藏的表单,最后用js带上表单跳转到其他的页面。因此在firebug的监控的request中,会莫名其妙的多出来一些参数。
6. xpath表达式
能简单尽量简单,尽量class和id来表达。千万别将xpath的表达式依赖很多属性,这样难以维护不说,而且极不稳定。
7. xpath表达式
xpath表达式能在浏览器中找到元素,在spider里确不能,有可能性js动态加载,还有table元素,有的会自动添加th等。这个时候请直接对着html源码写你的表达式。
8. ajax request和json,这应该是现在网站的主流。
要关注的真正得到数据的request,通常我们要抓取的数据也都在ajax request中,拿到数据后用json.loads(response.body)
9. 要抓取的内容分布在不同的request中,这个时候request meta就能很好的胜任。
def parse_city_item(self, response):
x = HtmlXPathSelector(response)
item = Item()
//给item赋值
item['title'] = ''.join(x.select('//div[@class="title"]/text()').extract())
data = {}
req = FormRequest(url=url,formdata=data,callback=self.parse_comment)
req.meta['item'] = item
//带上返回req
return req
def parse_comment(self, response):
item = response.request.meta['item']
x = HtmlXPathSelector(response)
item['content'] = ''.join(x.select('//div[@class="content"]/text()').extract())
return item
10. 增量抓取
很多情况下需要抓取网站更新的内容。我们知道在一次抓取的过程是能避免重复抓取,scrapy默认提供文件存储的方式,只需要再settings里设置JOBDIR=”path”。我在使用scrapy还是0.9,没有这个特性,使用redis作为url存储。个人感觉对于大规模抓取用redis还是比文件的方式要好很多。redis里可以设置key的过期时间,肯定会有人说,这样能保证不重复的抓取吗,当然不能绝对,但是可以调整抓取深度,对于抓取较为频繁网站,抓取到相同的概率就很低。比如说抓取sina的体育新闻,将url做md5加密存储到redis里,过期时间设置为1天,抓取体育新闻滚动页面前3页 点击链接,15分钟抓取一次。而单纯用文件方式存储的话,文件只大不小,多了自然影响性能。
11. 编码
上一篇也提到过。scrapy为我们做了编码,但是如果这样好错了,就需要特殊处理了。
参考自:http://www.cnblogs.com/twelfthing/articles/4620533.html
http://www.cnblogs.com/twelfthing/articles/4620761.html