<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0">
<channel>
<title><![CDATA[向东博客 专注WEB应用 构架之美 --- 构架之美，在于尽态极妍 | 应用之美，在于药到病除]]></title> 
<link>http://jackxiang.com/index.php</link> 
<description><![CDATA[赢在IT，Playin' with IT,Focus on Killer Application,Marketing Meets Technology.]]></description> 
<language>zh-cn</language> 
<copyright><![CDATA[向东博客 专注WEB应用 构架之美 --- 构架之美，在于尽态极妍 | 应用之美，在于药到病除]]></copyright>
<item>
<link>http://jackxiang.com/post//</link>
<title><![CDATA[用python爬虫抓站的一些技巧总结]]></title> 
<author>jack &lt;xdy108@126.com&gt;</author>
<category><![CDATA[Php/Js/Shell/Go]]></category>
<pubDate>Tue, 07 Aug 2012 06:36:23 +0000</pubDate> 
<guid>http://jackxiang.com/post//</guid> 
<description>
<![CDATA[ 
	这一篇文章写得不错，特转载，Url如下：<br/>来自：<br/>http://obmem.info/?p=476<br/>http://obmem.info/?p=753<br/><br/><br/>学用python也有3个多月了，用得最多的还是各类爬虫脚本：写过抓代理本机验证的脚本，写过在discuz论坛中自动登录自动发贴的脚本，写过自动收邮件的脚本，写过简单的验证码识别的脚本，本来想写google music的抓取脚本的，结果有了强大的gmbox，也就不用写了。<br/> -<br/> 这些脚本有一个共性，都是和web相关的，总要用到获取链接的一些方法，再加上simplecd这个半爬虫半网站的项目，累积不少爬虫抓站的经验，在此总结一下，那么以后做东西也就不用重复劳动了。<br/>-<br/> 1.最基本的抓站<br/>import urllib2<br/>content = urllib2.urlopen(&#039;http://XXXX&#039;).read()<br/>-<br/> 2.使用代理服务器<br/> 这在某些情况下比较有用，比如IP被封了，或者比如IP访问的次数受到限制等等。<br/>import urllib2<br/>proxy_support = urllib2.ProxyHandler(&#123;&#039;http&#039;:&#039;http://XX.XX.XX.XX:XXXX&#039;&#125;)<br/>opener = urllib2.build_opener(proxy_support, urllib2.HTTPHandler)<br/>urllib2.install_opener(opener)<br/>content = urllib2.urlopen(&#039;http://XXXX&#039;).read()<br/>-<br/> 3.需要登录的情况<br/> 登录的情况比较麻烦我把问题拆分一下：<br/> -<br/> 3.1 cookie的处理<br/>import urllib2, cookielib<br/>cookie_support= urllib2.HTTPCookieProcessor(cookielib.CookieJar())<br/>opener = urllib2.build_opener(cookie_support, urllib2.HTTPHandler)<br/>urllib2.install_opener(opener)<br/>content = urllib2.urlopen(&#039;http://XXXX&#039;).read()<br/>是的没错，如果想同时用代理和cookie，那就加入proxy_support然后operner改为<br/>opener = urllib2.build_opener(proxy_support, cookie_support, urllib2.HTTPHandler)<br/>-<br/> 3.2 表单的处理<br/> 登录必要填表，表单怎么填？首先利用工具截取所要填表的内容<br/> 比如我一般用firefox+httpfox插件来看看自己到底发送了些什么包<br/> 这个我就举个例子好了，以verycd为例，先找到自己发的POST请求，以及POST表单项：<br/>-<br/> 可以看到verycd的话需要填username,password,continueURI,fk,login_submit这几项，其中fk是随机生成的（其实不太随机，看上去像是把epoch时间经过简单的编码生成的），需要从网页获取，也就是说得先访问一次网页，用正则表达式等工具截取返回数据中的fk项。continueURI顾名思义可以随便写，login_submit是固定的，这从源码可以看出。还有username，password那就很显然了。<br/> -<br/> 好的，有了要填写的数据，我们就要生成postdata<br/>import urllib<br/>postdata=urllib.urlencode(&#123;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&#039;username&#039;:&#039;XXXXX&#039;,<br/>&nbsp;&nbsp;&nbsp;&nbsp;&#039;password&#039;:&#039;XXXXX&#039;,<br/>&nbsp;&nbsp;&nbsp;&nbsp;&#039;continueURI&#039;:&#039;http://www.verycd.com/&#039;,<br/>&nbsp;&nbsp;&nbsp;&nbsp;&#039;fk&#039;:fk,<br/>&nbsp;&nbsp;&nbsp;&nbsp;&#039;login_submit&#039;:&#039;登录&#039;<br/>&#125;)<br/>-<br/> 然后生成http请求，再发送请求：<br/>req = urllib2.Request(<br/>&nbsp;&nbsp;&nbsp;&nbsp;url = &#039;http://secure.verycd.com/signin/*/http://www.verycd.com/&#039;,<br/>&nbsp;&nbsp;&nbsp;&nbsp;data = postdata<br/>)<br/>result = urllib2.urlopen(req).read()<br/>-<br/> 3.3 伪装成浏览器访问<br/> 某些网站反感爬虫的到访，于是对爬虫一律拒绝请求<br/> 这时候我们需要伪装成浏览器，这可以通过修改http包中的header来实现<br/> #…<br/>headers = &#123;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&#039;User-Agent&#039;:&#039;Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6&#039;<br/>&#125;<br/>req = urllib2.Request(<br/>&nbsp;&nbsp;&nbsp;&nbsp;url = &#039;http://secure.verycd.com/signin/*/http://www.verycd.com/&#039;,<br/>&nbsp;&nbsp;&nbsp;&nbsp;data = postdata,<br/>&nbsp;&nbsp;&nbsp;&nbsp;headers = headers<br/>)<br/>#...<br/>-<br/> 3.4 反”反盗链”<br/> 某些站点有所谓的反盗链设置，其实说穿了很简单，就是检查你发送请求的header里面，referer站点是不是他自己，所以我们只需要像3.3一样，把headers的referer改成该网站即可，以黑幕著称地cnbeta为例：<br/>#...<br/>headers = &#123;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&#039;Referer&#039;:&#039;http://www.cnbeta.com/articles&#039;<br/>&#125;<br/>#...<br/>headers是一个dict数据结构，你可以放入任何想要的header，来做一些伪装。例如，有些自作聪明的网站总喜欢窥人隐私，别人通过代理访问，他偏偏要读取header中的X-Forwarded-For来看看人家的真实IP，没话说，那就直接把X-Forwarde-For改了吧，可以改成随便什么好玩的东东来欺负欺负他，呵呵。<br/> -<br/> 3.5 终极绝招<br/> 有时候即使做了3.1-3.4，访问还是会被据，那么没办法，老老实实把httpfox中看到的headers全都写上，那一般也就行了。<br/> 再不行，那就只能用终极绝招了，selenium直接控制浏览器来进行访问，只要浏览器可以做到的，那么它也可以做到。类似的还有pamie，watir，等等等等。<br/> -<br/> 4.多线程并发抓取<br/> 单线程太慢的话，就需要多线程了，这里给个简单的线程池模板<br/> 这个程序只是简单地打印了1-10，但是可以看出是并发地。<br/>from threading import Thread<br/>from Queue import Queue<br/>from time import sleep<br/>#q是任务队列<br/>#NUM是并发线程总数<br/>#JOBS是有多少任务<br/>q = Queue()<br/>NUM = 2<br/>JOBS = 10<br/>#具体的处理函数，负责处理单个任务<br/>def do_somthing_using(arguments):<br/>&nbsp;&nbsp;&nbsp;&nbsp;print arguments<br/>#这个是工作进程，负责不断从队列取数据并处理<br/>def working():<br/>&nbsp;&nbsp;&nbsp;&nbsp;while True:<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;arguments = q.get()<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;do_somthing_using(arguments)<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sleep(1)<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;q.task_done()<br/>#fork NUM个线程等待队列<br/>for i in range(NUM):<br/>&nbsp;&nbsp;&nbsp;&nbsp;t = Thread(target=working)<br/>&nbsp;&nbsp;&nbsp;&nbsp;t.setDaemon(True)<br/>&nbsp;&nbsp;&nbsp;&nbsp;t.start()<br/>#把JOBS排入队列<br/>for i in range(JOBS):<br/>&nbsp;&nbsp;&nbsp;&nbsp;q.put(i)<br/>#等待所有JOBS完成<br/>q.join()<br/>5.验证码的处理<br/> 碰到验证码咋办？这里分两种情况处理：<br/> -<br/> 1.google那种验证码，凉拌<br/> -<br/> 2.简单的验证码：字符个数有限，只使用了简单的平移或旋转加噪音而没有扭曲的，这种还是有可能可以处理的，一般思路是旋转的转回来，噪音去掉，然后划分单个字符，划分好了以后再通过特征提取的方法(例如PCA)降维并生成特征库，然后把验证码和特征库进行比较。这个比较复杂，一篇博文是说不完的，这里就不展开了，具体做法请弄本相关教科书好好研究一下。<br/> -<br/> 3.事实上有些验证码还是很弱的，这里就不点名了，反正我通过2的方法提取过准确度非常高的验证码，所以2事实上是可行的。<br/> -<br/> 6.总结<br/> 基本上我遇到过的所有情况，用以上方法都顺利解决了，不太清楚还有没有其他漏掉的情况，所以本文到这里就完成了，以后要是碰上其他情况，再补充相关方法好了：）<br/>本文后续见： http://obmem.info/?p=753<br/>以前写过一篇使用python爬虫抓站的一些技巧总结，总结了诸多爬虫使用的方法；那篇东东现在看来还是挺有用的，但是当时很菜（现在也菜，但是比那时进步了不少），很多东西都不是很优，属于”只是能用”这么个层次。这篇进阶篇打算把“能用”提升到“用得省事省心”这个层次。<br/>一、gzip/deflate支持<br/>现在的网页普遍支持gzip压缩，这往往可以解决大量传输时间，以VeryCD的主页为例，未压缩版本247K，压缩了以后45K，为原来的1/5。这就意味着抓取速度会快5倍。<br/>然而python的urllib/urllib2默认都不支持压缩，要返回压缩格式，必须在request的header里面写明’accept-encoding’，然后读取response后更要检查header查看是否有’content-encoding’一项来判断是否需要解码，很繁琐琐碎。如何让urllib2自动支持gzip, defalte呢？<br/>其实可以继承BaseHanlder类，然后build_opener的方式来处理：<br/>import urllib2<br/>from gzip import GzipFile<br/>from StringIO import StringIO<br/>class ContentEncodingProcessor(urllib2.BaseHandler):<br/>&nbsp;&nbsp;&quot;&quot;&quot;A handler to add gzip capabilities to urllib2 requests &quot;&quot;&quot;<br/>&nbsp;&nbsp;# add headers to requests<br/>&nbsp;&nbsp;def http_request(self, req):<br/>&nbsp;&nbsp;&nbsp;&nbsp;req.add_header(&quot;Accept-Encoding&quot;, &quot;gzip, deflate&quot;)<br/>&nbsp;&nbsp;&nbsp;&nbsp;return req<br/>&nbsp;&nbsp;# decode<br/>&nbsp;&nbsp;def http_response(self, req, resp):<br/>&nbsp;&nbsp;&nbsp;&nbsp;old_resp = resp<br/>&nbsp;&nbsp;&nbsp;&nbsp;# gzip<br/>&nbsp;&nbsp;&nbsp;&nbsp;if resp.headers.get(&quot;content-encoding&quot;) == &quot;gzip&quot;:<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;gz = GzipFile(<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fileobj=StringIO(resp.read()),<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mode=&quot;r&quot;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;)<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;resp = urllib2.addinfourl(gz, old_resp.headers, old_resp.url, old_resp.code)<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;resp.msg = old_resp.msg<br/>&nbsp;&nbsp;&nbsp;&nbsp;# deflate<br/>&nbsp;&nbsp;&nbsp;&nbsp;if resp.headers.get(&quot;content-encoding&quot;) == &quot;deflate&quot;:<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;gz = StringIO( deflate(resp.read()) )<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;resp = urllib2.addinfourl(gz, old_resp.headers, old_resp.url, old_resp.code)&nbsp;&nbsp;# &#039;class to add info() and<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;resp.msg = old_resp.msg<br/>&nbsp;&nbsp;&nbsp;&nbsp;return resp<br/># deflate support<br/>import zlib<br/>def deflate(data):&nbsp;&nbsp; # zlib only provides the zlib compress format, not the deflate format;<br/>&nbsp;&nbsp;try:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; # so on top of all there&#039;s this workaround:<br/>&nbsp;&nbsp;&nbsp;&nbsp;return zlib.decompress(data, -zlib.MAX_WBITS)<br/>&nbsp;&nbsp;except zlib.error:<br/>&nbsp;&nbsp;&nbsp;&nbsp;return zlib.decompress(data)<br/>然后就简单了，<br/>encoding_support = ContentEncodingProcessor<br/>opener = urllib2.build_opener( encoding_support, urllib2.HTTPHandler )<br/>#直接用opener打开网页，如果服务器支持gzip/defalte则自动解压缩<br/>content = opener.open(url).read()<br/>二、更方便地多线程<br/>总结一文的确提及了一个简单的多线程模板，但是那个东东真正应用到程序里面去只会让程序变得支离破碎，不堪入目。在怎么更方便地进行多线程方面我也动了一番脑筋。先想想怎么进行多线程调用最方便呢？<br/>1、用twisted进行异步I/O抓取<br/>事实上更高效的抓取并非一定要用多线程，也可以使用异步I/O法：直接用twisted的getPage方法，然后分别加上异步I/O结束时的callback和errback方法即可。例如可以这么干：<br/>from twisted.web.client import getPage<br/>from twisted.internet import reactor<br/>links = [ &#039;http://www.verycd.com/topics/%d/&#039;%i for i in range(5420,5430) ]<br/>def parse_page(data,url):<br/>&nbsp;&nbsp;&nbsp;&nbsp;print len(data),url<br/>def fetch_error(error,url):<br/>&nbsp;&nbsp;&nbsp;&nbsp;print error.getErrorMessage(),url<br/># 批量抓取链接<br/>for url in links:<br/>&nbsp;&nbsp;&nbsp;&nbsp;getPage(url,timeout=5) &#92;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.addCallback(parse_page,url) &#92; #成功则调用parse_page方法<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.addErrback(fetch_error,url)&nbsp;&nbsp;&nbsp;&nbsp; #失败则调用fetch_error方法<br/>reactor.callLater(5, reactor.stop) #5秒钟后通知reactor结束程序<br/>reactor.run()<br/>twisted人如其名，写的代码实在是太扭曲了，非正常人所能接受，虽然这个简单的例子看上去还好；每次写twisted的程序整个人都扭曲了，累得不得了，文档等于没有，必须得看源码才知道怎么整，唉不提了。<br/>如果要支持gzip/deflate，甚至做一些登陆的扩展，就得为twisted写个新的HTTPClientFactory类诸如此类，我这眉头真是大皱，遂放弃。有毅力者请自行尝试。<br/>这篇讲怎么用twisted来进行批量网址处理的文章不错，由浅入深，深入浅出，可以一看。<br/>2、设计一个简单的多线程抓取类<br/>还是觉得在urllib之类python“本土”的东东里面折腾起来更舒服。试想一下，如果有个Fetcher类，你可以这么调用<br/>f = Fetcher(threads=10) #设定下载线程数为10<br/>for url in urls:<br/>&nbsp;&nbsp;&nbsp;&nbsp;f.push(url)&nbsp;&nbsp;#把所有url推入下载队列<br/>while f.taskleft(): #若还有未完成下载的线程<br/>&nbsp;&nbsp;&nbsp;&nbsp;content = f.pop()&nbsp;&nbsp;#从下载完成队列中取出结果<br/>&nbsp;&nbsp;&nbsp;&nbsp;do_with(content) # 处理content内容<br/>这么个多线程调用简单明了，那么就这么设计吧，首先要有两个队列，用Queue搞定，多线程的基本架构也和“技巧总结”一文类似，push方法和pop方法都比较好处理，都是直接用Queue的方法，taskleft则是如果有“正在运行的任务”或者”队列中的任务”则为是，也好办，于是代码如下：<br/>import urllib2<br/>from threading import Thread,Lock<br/>from Queue import Queue<br/>import time<br/>class Fetcher:<br/>&nbsp;&nbsp;&nbsp;&nbsp;def __init__(self,threads):<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self.opener = urllib2.build_opener(urllib2.HTTPHandler)<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self.lock = Lock() #线程锁<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self.q_req = Queue() #任务队列<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self.q_ans = Queue() #完成队列<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self.threads = threads<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for i in range(threads):<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;t = Thread(target=self.threadget)<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;t.setDaemon(True)<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;t.start()<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self.running = 0<br/>&nbsp;&nbsp;&nbsp;&nbsp;def __del__(self): #解构时需等待两个队列完成<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;time.sleep(0.5)<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self.q_req.join()<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self.q_ans.join()<br/>&nbsp;&nbsp;&nbsp;&nbsp;def taskleft(self):<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return self.q_req.qsize()+self.q_ans.qsize()+self.running<br/>&nbsp;&nbsp;&nbsp;&nbsp;def push(self,req):<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self.q_req.put(req)<br/>&nbsp;&nbsp;&nbsp;&nbsp;def pop(self):<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return self.q_ans.get()<br/>&nbsp;&nbsp;&nbsp;&nbsp;def threadget(self):<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;while True:<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;req = self.q_req.get()<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;with self.lock: #要保证该操作的原子性，进入critical area<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self.running += 1<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;try:<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ans = self.opener.open(req).read()<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;except Exception, what:<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ans = &#039;&#039;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;print what<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self.q_ans.put((req,ans))<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;with self.lock:<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self.running -= 1<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self.q_req.task_done()<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;time.sleep(0.1) # don&#039;t spam<br/>if __name__ == &quot;__main__&quot;:<br/>&nbsp;&nbsp;&nbsp;&nbsp;links = [ &#039;http://www.verycd.com/topics/%d/&#039;%i for i in range(5420,5430) ]<br/>&nbsp;&nbsp;&nbsp;&nbsp;f = Fetcher(threads=10)<br/>&nbsp;&nbsp;&nbsp;&nbsp;for url in links:<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;f.push(url)<br/>&nbsp;&nbsp;&nbsp;&nbsp;while f.taskleft():<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;url,content = f.pop()<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;print url,len(content)<br/>三、一些琐碎的经验<br/>1、连接池：<br/>opener.open和urllib2.urlopen一样，都会新建一个http请求。通常情况下这不是什么问题，因为线性环境下，一秒钟可能也就新生成一个请求；然而在多线程环境下，每秒钟可以是几十上百个请求，这么干只要几分钟，正常的有理智的服务器一定会封禁你的。<br/>然而在正常的html请求时，保持同时和服务器几十个连接又是很正常的一件事，所以完全可以手动维护一个HttpConnection的池，然后每次抓取时从连接池里面选连接进行连接即可。<br/>这里有一个取巧的方法，就是利用squid做代理服务器来进行抓取，则squid会自动为你维护连接池，还附带数据缓存功能，而且squid本来就是我每个服务器上面必装的东东，何必再自找麻烦写连接池呢。<br/>2、设定线程的栈大小<br/>栈大小的设定将非常显著地影响python的内存占用，python多线程不设置这个值会导致程序占用大量内存，这对openvz的vps来说非常致命。stack_size必须大于32768，实际上应该总要32768*2以上<br/>from threading import stack_size<br/>stack_size(32768*16)<br/>3、设置失败后自动重试<br/>&nbsp;&nbsp;&nbsp;&nbsp;def get(self,req,retries=3):<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;try:<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;response = self.opener.open(req)<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;data = response.read()<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;except Exception , what:<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;print what,req<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if retries&gt;0:<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return self.get(req,retries-1)<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else:<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;print &#039;GET Failed&#039;,req<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return &#039;&#039;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return data<br/>4、设置超时<br/>&nbsp;&nbsp;&nbsp;&nbsp;import socket<br/>&nbsp;&nbsp;&nbsp;&nbsp;socket.setdefaulttimeout(10) #设置10秒后连接超时<br/>5、登陆<br/>登陆更加简化了，首先build_opener中要加入cookie支持，参考“总结”一文；如要登陆VeryCD，给Fetcher新增一个空方法login，并在__init__()中调用，然后继承Fetcher类并override login方法：<br/>def login(self,username,password):<br/>&nbsp;&nbsp;&nbsp;&nbsp;import urllib<br/>&nbsp;&nbsp;&nbsp;&nbsp;data=urllib.urlencode(&#123;&#039;username&#039;:username,<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#039;password&#039;:password,<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#039;continue&#039;:&#039;http://www.verycd.com/&#039;,<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#039;login_submit&#039;:u&#039;登录&#039;.encode(&#039;utf-8&#039;),<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#039;save_cookie&#039;:1,&#125;)<br/>&nbsp;&nbsp;&nbsp;&nbsp;url = &#039;http://www.verycd.com/signin&#039;<br/>&nbsp;&nbsp;&nbsp;&nbsp;self.opener.open(url,data).read()<br/>于是在Fetcher初始化时便会自动登录VeryCD网站。<br/>四、总结<br/>如此，把上述所有小技巧都糅合起来就和我目前的私藏最终版的Fetcher类相差不远了，它支持多线程，gzip/deflate压缩，超时设置，自动重试，设置栈大小，自动登录等功能；代码简单，使用方便，性能也不俗，可谓居家旅行，杀人放火，咳咳，之必备工具。<br/>之所以说和最终版差得不远，是因为最终版还有一个保留功能“马甲术”：多代理自动选择。看起来好像仅仅是一个random.choice的区别，其实包含了代理获取，代理验证，代理测速等诸多环节，这就是另一个故事了。<br/>
]]>
</description>
</item><item>
<link>http://jackxiang.com/post//#blogcomment</link>
<title><![CDATA[[评论] 用python爬虫抓站的一些技巧总结]]></title> 
<author> &lt;user@domain.com&gt;</author>
<category><![CDATA[评论]]></category>
<pubDate>Thu, 01 Jan 1970 00:00:00 +0000</pubDate> 
<guid>http://jackxiang.com/post//#blogcomment</guid> 
<description>
<![CDATA[ 
	
]]>
</description>
</item>
</channel>
</rss>