模拟登录
研究源码
以 github 登录(https://github.com/login) 为例,查看html源码会发现表单里面有个隐藏的authenticity_token值,这个是需要先获取然后跟用户名和密码一起提交的。
重写start_requests方法
首先确保 cookie 打开
COOKIES_ENABLES = True
重写start_requests方法
# 重写了爬虫类的方法, 实现了自定义请求, 运行成功后会调用callback回调函数 def start_requests(self): return [Request("https://github.com/login", meta={'cookiejar': 1}, callback=self.post_login)] # FormRequeset def post_login(self, response): # 先去拿隐藏的表单参数authenticity_token authenticity_token = response.xpath( '//input[@name="authenticity_token"]/@value').extract_first() logging.info('authenticity_token=' + authenticity_token) pass
start_requests方法指定了回调函数,用来获取隐藏表单值authenticity_token,同时我们还给Request指定了cookiejar的元数据,用来往回调函数传递cookie标识。
使用FormRequest
Scrapy为我们准备了FormRequest类专门用来进行Form表单提交的。
# FormRequeset def post_login(self, response): # 先去拿隐藏的表单参数authenticity_token authenticity_token = response.xpath( '//input[@name="authenticity_token"]/@value').extract_first() logging.info('authenticity_token=' + authenticity_token) # FormRequeset.from_response是Scrapy提供的一个函数, 用于post表单 # 登陆成功后, 会调用after_login回调函数,如果url跟Request页面的一样就省略掉 return [FormRequest.from_response(response, url='https://github.com/session', meta={'cookiejar': response.meta['cookiejar']}, #headers=self.post_headers, formdata={ 'login': 'shuang0420', 'password': 'XXXXXXXXXXXXXXXXX', 'authenticity_token': authenticity_token }, callback=self.after_login, dont_filter=True )]
FormRequest.from_response()方法让你指定提交的url,请求头还有form表单值,注意我们还通过meta传递了cookie标识。它同样有个回调函数,登录成功后调用。下面我们来实现它。注意这里我继续传递cookiejar,访问初始页面时带上cookie信息。
def after_login(self, response): # 登录之后,开始进入我要爬取的私信页面 for url in self.start_urls: logging.info('letter url=' + url) yield Request(url, meta={'cookiejar': response.meta['cookiejar']},callback=self.parse_page)
页面处理
这个例子的主要任务是模拟登录,在登录 github 后爬取主页的 comments 内容。
代码
爬取结果
I like topn (or perhaps top_n) a little better, because it's not dependent on what the features represent (words, phrases, entities, characters...). … Note: as of now, the classes and methods are not well arranged, and there are a few mock classes (which will be removed) to help me with testing. O… Hello @gojomo thank you for replying fast.I have used save() to save the model and load_word2vec_format() to load the model. Thats where the probl… The unicode_errors='ignore' option should make it impossible for the exact same error to occur; perhaps you're getting some other very-similar error? (Nevermind, #758 added annoy.) It looks like the tests don't run on Travis, since Annoy is not installed there. Not sure how to fix the test failure in Python 2.6 either. Hello,Sorry for posting after even you have created the FAQ.I trained a model with tweets which had some undecodable unicode characters. When i t… dtto Misleading comment: there is no "training", the model is transferred from Mallet. These parameters only affect inference, model is unchanged. PEP8: Hanging indent of 4 spaces. @piskvorky I've addressed the comments. Could you please check? Thanks, that was quick :) @piskvorky , @tmylk , could you review? Added comment, made change in changelog. No, this was after that in 0.13.2. I noticed it because when I was testing the #768 solution, print_topics was failing. @tmylk how do you review these PRs before merging? There are too many errors, we cannot merge code so carelessly. Looks good to me... except still needs a comment explaining why the alias is there. And maybe a mention in the changelog, so we can deprecate the o… Yes, assign self.wordtopics = self.word_topics, with a big fat comment explaining why this alias is there. I don't understand how this version with storing unicode to binary files even worked. It means our unit tests must be faulty / incomplete.
识别验证码
验证码是一种非常有效的反爬虫机制,它能阻止大部分的暴力抓取,在电商类、投票类以及社交类等网站上应用广泛。如果破解验证码,成为了数据抓取工作者必须要面对的问题。下面介绍3种常用的方法。
更换ip地址
在访问某些网站时,我们最初只是需要提供用户名密码就可以登陆的,比如说豆瓣网,如果我们要是频繁登陆访问,可能这时网站就会出现一个验证码图片,要求我们输入验证码才能登陆,这样在保证用户方便访问的同时,又防止了机器的恶意频繁访问。对于这种情况,我们可以使用代理服务器访问,只需要换个ip地址再次访问,验证码就不会出现了,当然,当验证码再次出现的时候,我们只能再更换ip地址。
使用cookie登陆
如果采用cookie登陆,可以这样实现:首先需要手动登陆网站一次,获取服务器返回的cookie,这里就带有了用户的登陆信息,当然也可以采用获取的cookie登陆该网站的其他页面,而不用再次登陆。具体代码已经实现,详见ZhihuSpider。我们只需要在配置文件中提供用户名密码,及相应的cookie即可。对于不出现验证码的情况,爬虫会提交用户名密码实现post请求登陆,如果失败,才会使用事先提供的cookie信息。
需要说明的是,判断爬虫登陆与否,我们只需要看一下爬取的信息里面是否带有用户信息即可。在使用cookie登陆的时候,还需要不定期更新cookie,以保证爬取顺利进行。
验证码识别手段
使用cookie登陆比较简单,但是有时效性问题。验证码识别是个很好的思路,然而识别的精度又限制了抓取的效率。
爬取js交互式表格数据
这里,若使用Google Chrome分析”请求“对应的链接(方法:右键→审查元素→Network→清空,点击”加载更多“,出现对应的GET链接寻找Type为text/html的,点击,查看get参数或者复制Request URL),循环过程。
启动 splash 容器
$ docker run -p 8050:8050 scrapinghub/splash
配置 scrapy-splash
在你的 scrapy 工程的配置文件settings.py中添加
SPLASH_URL = 'http://192.168.59.103:8050' # 添加Splash中间件,还是在settings.py中通过DOWNLOADER_MIDDLEWARES指定,并且修改HttpCompressionMiddleware的优先级 DOWNLOADER_MIDDLEWARES = { 'scrapy_splash.SplashCookiesMiddleware': 723, 'scrapy_splash.SplashMiddleware': 725, 'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware': 810, } # 默认情况下,HttpProxyMiddleware的优先级是750,要把它放在Splash中间件后面 # 设置Splash自己的去重过滤器 DUPEFILTER_CLASS = 'scrapy_splash.SplashAwareDupeFilter' # 如果你使用Splash的Http缓存,那么还要指定一个自定义的缓存后台存储介质,scrapy-splash提供了一个scrapy.contrib.httpcache.FilesystemCacheStorage的子类 HTTPCACHE_STORAGE = 'scrapy_splash.SplashAwareFSCacheStorage' # 如果你要使用其他的缓存存储,那么需要继承这个类并且将所有的scrapy.util.request.request_fingerprint调用替换成scrapy_splash.splash_request_fingerprint
使用 scrapy-splash
SplashRequest
最简单的渲染请求的方式是使用scrapy_splash.SplashRequest,通常你应该选择使用这个
另外,你还可以在普通的scrapy请求中传递splash请求meta关键字达到同样的效果
Splash API说明,使用SplashRequest是一个非常便利的工具来填充request.meta[‘splash’]里的数据
- meta[‘splash’][‘args’] 包含了发往Splash的参数。
- meta[‘splash’][‘endpoint’] 指定了Splash所使用的endpoint,默认是render.html
- meta[‘splash’][‘splash_url’] 覆盖了settings.py文件中配置的Splash URL
- meta[‘splash’][‘splash_headers’] 运行你增加或修改发往Splash服务器的HTTP头部信息,注意这个不是修改发往远程web站点的HTTP头部
- meta[‘splash’][‘dont_send_headers’] 如果你不想传递headers给Splash,将它设置成True
- meta[‘splash’][‘slot_policy’] 让你自定义Splash请求的同步设置
- meta[‘splash’][‘dont_process_response’] 当你设置成True后,SplashMiddleware不会修改默认的scrapy.Response请求。默认是会返回SplashResponse子类响应比如SplashTextResponse
- meta[‘splash’][‘magic_response’] 默认为True,Splash会自动设置Response的一些属性,比如response.headers,response.body等
如果你想通过Splash来提交Form请求,可以使用scrapy_splash.SplashFormRequest,它跟SplashRequest使用是一样的。
Responses
对于不同的Splash请求,scrapy-splash返回不同的Response子类
- SplashResponse 二进制响应,比如对/render.png的响应
- SplashTextResponse 文本响应,比如对/render.html的响应
- SplashJsonResponse JSON响应,比如对/render.json或使用Lua脚本的/execute的响应
如果你只想使用标准的Response对象,就设置meta[‘splash’][‘dont_process_response’]=True
所有这些Response会把response.url设置成原始请求URL(也就是你要渲染的页面URL),而不是Splash endpoint的URL地址。实际地址通过response.real_url得到
实例
爬取华为应用市场( http://appstore.huawei.com/more/all )的“下一页” url 链接。
查看网页源代码
|
|
查看渲染后的代码
启动 splash 容器,在浏览器打开 http://192.168.59.103:8050/ , 输入网址进行 render,查看渲染后的代码。
spider 部分代码
def parse(self, response): page = Selector(response) hrefs = page.xpath('//h4[@class="title"]/a/@href') if not hrefs: return for href in hrefs: url = href.extract() yield scrapy.Request(url, callback=self.parse_item) # find next page nextpage = page.xpath('//div[@class="page-ctrl ctrl-app"]/a/em[@class="arrow-grey-rt"]/../@href').extract_first() print nextpage yield scrapy.Request(nextpage,callback=self.parse,meta={ 'splash': { 'endpoint': 'render.html', 'args': {'wait': 0.5} } })
分析不规则的 html
之前的几个部分解决的都是 下载 Web 页面 的问题,这里补充下获取网页后分析过程的一些技巧。
以苏宁易购 help 页面为例。start_url 是 http://help.suning.com/faq/list.htm , 爬取的是左边侧栏每个大类的每个小类下右边的问题页面,如“权益介绍”、“等级权益介绍”这些 FAQ 页面,如何到达这些页面就不再多说,关键是到达这些页面后怎么获得信息。
看一部分的网页源代码
不难发现,有些文字分布在
- div[@id=”contentShow”]/p
- div[@id=”contentShow”]/p/span
- div[@id=”contentShow”]/p/b
观察其他页面会发现还有些分布在 div[@id=”contentShow”]/h4 下或者 h3 下,有的甚至直接就在 div[@id=”contentShow”] 下。。
怎么办?
当然可以穷尽各种规则,也可以先把不需要的标签给去掉再 extract,这些我开始都傻傻的尝试过,结果总会忽略一些文字,后来在沮丧的看着 output 文件时福至心灵,直接取了 div[@id=”contentShow”] 再把所有的标签去掉不就行了?!
上代码
最后的结果非常干净
掌握这个技巧,处理类似问题就很简单啦,如再爬京东的 help 网页,稍微改下代码5分钟就能搞定。
其他
回头谈点背景知识,scrapy使用了twisted.一个异步网络框架.因此要留意潜在的阻塞情况.但注意到settings中有个参数是设置ItemPipeline的并行度.由此推测pipeline不会阻塞,pipeline可能是在线程池中执行的(未验证).Pipeline一般用于将抓取到的信息保存(写数据库,写文件),因此这里你就不用担心耗时操作会阻塞整个框架了,也就不用在Pipeline中将这个写操作实现为异步.
除此之外框架的其他部分.都是异步的,简单说来就是,爬虫生成的请求交由调度器去下载,然后爬虫继续执行.调度器完成下载后会将响应交由爬虫解析.
网上找到的参考例子,部分将js支持写到了DownloaderMiddleware中,scrapy官网的code snippet也是这样 .若这样实现,就阻塞了整个框架,爬虫的工作模式变成了,下载-解析-下载-解析,而不在是并行的下载.在对效率要求不高的小规模爬取中问题不大.
更好的做法是将js支持写到scrapy的downloader里.网上有一个这样的实现(使用selenium+phantomjs).不过仅支持get请求.
在适配一个webkit给scrapy的downloader时,有各种细节需要处理.
参考链接
scrapy定制爬虫-爬取javascript内容
Scrapy笔记(11)- 模拟登录
网络爬虫-验证码登陆
Scrapy笔记(12)- 抓取动态网站