进阶——scrapy登录豆瓣解决cookie传递问题并爬取用户参加过的同城活动©seven_clear
最近在用scrapy重写以前的爬虫,由于豆瓣的某些信息要登录后才有权限查看,故要实现登录功能。豆瓣登录偶尔需要输入验证码,这个在以前写的爬虫里解决了验证码的问题,所以只要搞清楚scrapy怎么提交表单什么的就OK了。从网上找了点资料,说要重写CrawlSpider的start_requests,在重写的函数里发个request,在其回调函数里提交表单。至于request是啥,参考scrapy文档(中文版:http://scrapy-chs.readthedocs.io/zh_CN/latest/intro/tutorial.html)。废话少说,上代码。
重写start_requests:
def start_requests(self):
return [Request('https://www.douban.com/accounts/login',
meta={'cookiejar': 1}
callback=self.post_login)]
Scrapy通过使用cookiejar Request meta key来支持单spider追踪多cookie session。 默认情况下其使用一个cookie jar(session),不过可以传递一个标示符来使用多个。如meta={'cookiejar': 1}这句,后面那个1就是标示符。下面就是post_login函数了,这个函数主要是提交登录表单,要处理有验证码和无验证码两种情况:
def post_login(self, response):
print 'Preparing login====', response.url
# s = 'index_nav'
html = urllib2.urlopen(response.url).read()
# print 'htnl:', html
# 验证码图片地址
imgurl = re.search('<img id="captcha_image" src="(.+?)" alt="captcha" class="captcha_image"/>', html)
if imgurl:
url = imgurl.group(1)
# 将图片保存至同目录下
res = urllib.urlretrieve(url, 'v.jpg')
# 获取captcha-id参数
captcha = re.search('<input type="hidden" name="captcha-id" value="(.+?)"/>', html)
if captcha:
vcode = raw_input('请输入图片上的验证码:')
return [FormRequest.from_response(response,
meta={'cookiejar': response.meta['cookiejar']},
formdata={
'source': 'index_nav',
# 'source': s,
'form_email': 'your_email',
'form_password': 'your_password',
'captcha-solution': vcode,
'captcha-id': captcha.group(1),
'user_login': '登录'
},
callback=self.after_login,
dont_filter=True)
]
return [FormRequest.from_response(response,
meta={'cookiejar': response.meta['cookiejar']},
formdata={
'source': 'index_nav',
# 'source': s,
'form_email': 'your_email',
'form_password': 'your_password'
},
callback=self.after_login,
dont_filter=True)
]
这里通过meta传递cookie(为什么要传cookie,因为cookiejar meta key不是“粘性的”),并且source:index_nav这句是必须的,如果没有这句会登录不上,dont_filter是为了不过滤,因为前面写了自定义的Rule(后面会贴出代码),下面就是回调的after_login函数了:
def after_login(self, response):
print 'after_login============' for url in self.start_urls:
print '======url:', url
# 上面定义了Rule,这里只需要生成初始爬取Request即可
req = Request(url, meta={'cookiejar': response.meta['cookiejar']}) yield req
这里同样用meta传递cookie,不过这样是不成功的,运行时老是302重定向,肯定是cookie没传过去,后来找到一种方法,要重写下面的这个方法:
def _requests_to_follow(self, response):
if not isinstance(response, HtmlResponse):
return
seen = set()
for n, rule in enumerate(self._rules):
links = [l for l in rule.link_extractor.extract_links(response) if l not in seen]
if links and rule.process_links:
links = rule.process_links(links)
for link in links:
seen.add(link)
r = Request(url=link.url, callback=self._response_downloaded)
# 下面这句是重写的
r.meta.update(rule=n, link_text=link.text, cookiejar=response.meta['cookiejar'])
yield rule.process_request(r)
重写那句是每次更新meta都将cookie也更新了,这样传递即可cookie。当然,我这么菜,肯定不是自己想出来的(->_->)©seven_clear
最关键一步,在配置文件里启用cookie,在settings里设置COOKIES_ENABLED=True。如果想看cookie的传递信息,可设置COOKIES_DEBIG=True(别问我怎么知道的,文档上看的)
登录问题解决了,就搞点事,网上爬电影的一大堆,不想盲目随大流,搞个同城爽一下,爬别的模块看下网页源码,稍微动一下脑子就能搞出来。首先要把规则写好,在rules里加上同城的Rule:
Rule(SgmlLinkExtractor(allow=(r'https://www.douban.com/location/people/[0-9a-zA-Z]*/events/attend$')),
callback='parse_eventlist'),
SgmLinkExtractor是用来筛选链接的,用法最好还是看文档(文档往往是最好的参考资料)。
哦哦,还得确定爬取哪些内容,写一个Item,随便打开几个活动页面,发现活动都有时间、地点、费用、类型、主办方(发起人)这五项,则爬取这些信息:
class EventItem(Item):
itype = Field() # 类型
title = Field() # 活动标题
time = Field() # 时间
address = Field() # 地点
fee = Field() # 费用
etype = Field() # 类型
actor = Field() # 发起人/主办方
写好Item,还要处理Item,ItemPipiLines就是干这事的,这里将数据写入json,如果爬的数据量很大,json就不太合适了,可以用JsonLinesItemExporter,每个Item一项,具体用法以后应该会写个例子。pipeline代码:
class EventPipeLine(object):
def __init__(self):
self.file = codecs.open('event.json', 'w', encoding='utf-8') def process_item(self, item, spider): if item['itype'] is not 'event':
return item
name = json.dumps('活动名:' + str(item['title']), ensure_ascii=False) + '\n'
time = json.dumps('时间:' + str(item['time']), ensure_ascii=False) + '\n'
address = json.dumps('地点:' + str(item['address']), ensure_ascii=False) + '\n'
fee = json.dumps('费用:' + str(item['fee']), ensure_ascii=False) + '\n'
etype = json.dumps('类型:' + str(item['etype']), ensure_ascii=False) + '\n'
actor = json.dumps('发起人/主办方:' + str(item['actor']), ensure_ascii=False) + '\n'
line = name + owner + time + address + fee + etype + actor
self.file.write(line)
self.file.write('==================================================\n')
return item def spider_closed(self, spider):
self.file.close()
这样的代码在网上烂大街了,自己看看理解思路就行,ensure_ascii=False解决写入时中文乱码问题。对了,item类里有两个类型,注释没写好,itype是说明Item类的类型,即这个item是处理什么类型的数据(电影、音乐或者同城等等),下面那个etype是活动详情页里的那个类型(活动的),在pipeline里用itype来判断item的类型,这只是一个思路,当爬的模块多的时候避免pipeline冲突,因为我爬的模块多,设置pipeline优先级后老冲突,爬取过程中返回的item顺序不一定和pipeline的顺序一致,于是就加了这么一个字段判断,当然要把各pipeline的优先级设一样(在settings里设置ITEM_PIPELINES)。
都OK了,可以写rule里的回调函数了,流程大致为:从用户主页筛选用户同城活动链接->进入链接后筛已过期链接(参加过的当然是已过期)->进入链接筛活动列表->对列表的活动进入详情页筛要爬的数据©seven_clear。一个三个函数:
def parse_eventlist(self, response):
print 'url=====', response.url
url = response.url
nextp = url + '/expired'
print 'nextpage:', nextp
req = Request(nextp, callback=self.event_list)
# ===========================================================================
req.meta['cookiejar'] = response.meta['cookiejar'] # 传递cookie
# ===========================================================================
yield req def event_list(self, response):
page = response.body
soup = BeautifulSoup(page, 'html.parser')
# 获取条目总页数
pagenum = soup.find("span", {"class", "thispage"})['data-total-page'] \
if soup.find("span", {"class", "thispage"}) else 0
print 'before====pagenum===:', pagenum
print 'type:', type(pagenum)
if int(pagenum) > 5:
pagenum = 5
print '====pagenum===:', pagenum
# 获取当前页数
thispage = soup.find("span", {"class", "thispage"}).next_element \
if soup.find("span", {"class", "thispage"}) else 0
print 'thispage:', thispage
# 获取下一页链接
if soup.find('span', {'class': 'next'}).find('a'):
nexturl = soup.find('span', {'class': 'next'}).find('a')['href']
nexturl = 'https://www.douban.com' + nexturl
print 'nexturl:', nexturl
result = soup.findAll("div", {"class": "info"})
print 'num:', len(result) - 1
for item in result:
if item.find('h1'): # 去除杂项
pass
else:
event_url = item.find('a')['href']
# print '========================================='
# print 'address:', item.find('a')['href']
req = Request(event_url, callback=self.parse_event) # 请求条目详情页
# ===========================================================================
req.meta['cookiejar'] = response.meta['cookiejar'] # 传递cookie
# ===========================================================================
yield req
print '==========page', thispage, 'print done!============'
if int(thispage) < int(pagenum): # 若当前页不到最后页或者指定页数,则请求下一页
req = Request(nexturl, callback=self.event_list)
# ===========================================================================
req.meta['cookiejar'] = response.meta['cookiejar'] # 传递cookie
# ===========================================================================
yield req def parse_event(self, response):
sel = Selector(response)
item = EventItem()
item['itype'] = 'event'
item['title'] = sel.xpath('//*[@id="event-info"]/div[1]/h1/text()').extract()[0].strip()
item['time'] = sel.xpath('//*[@id="event-info"]/div[1]/div[1]/ul/li/text()').extract()[0]
item['address'] = ''.join(sel.xpath('//*[@id="event-info"]/div[1]/div[2]/span[2]/span/text()').extract())
item['fee'] = sel.xpath('//*[@id="event-info"]/div[1]/div[3]/text()').extract()[1].strip()
item['etype'] = sel.xpath('//*[@id="event-info"]/div[1]/div[4]/a/text()').extract()[0]
item['actor'] = sel.xpath('//*[@id="event-info"]/div[1]/div[5]/a/text()').extract()[0]
yield item
在实现过程中还是有些坑的,比如判断总页数的时候,因为爬的数据是Unicode类型的,要转为int再与指定页数比较,不然可能会有这种情况(假设指定页数为5):活动总页数是4,但是if pagenum > 5的结果是True,导致爬取过程中出错;还有下一页的链接是不全的,要在前面加上前缀;活动的地址分级不一样,可以都爬下来用join方法连接起来。处理event信息是用xpath,这个很好用,可以看一下。
这样就O了,做出来觉得没什么,但做的过程是很曲折的,主要是思路的突破口,看了说一句这很简单也不容易,背后还是要付出点的,继续前行©seven_clear
对了,还有要import的东西,这里提一下:
from scrapy.contrib.spiders import CrawlSpider, Rule
from scrapy.contrib.linkextractors.sgml import SgmlLinkExtractor
from scrapy.selector import Selector # 选择器,xpath找数据
from ..items import EventItem # 导入item
import re #以下3个用于验证码登录时下载验证码
import urllib
import urllib2
from scrapy.http import Request, FormRequest, HtmlResponse # 表单登录
最后一句,我是菜鸟,别介意我的代码烂,哈哈哈
进阶——scrapy登录豆瓣解决cookie传递问题并爬取用户参加过的同城活动©seven_clear的更多相关文章
- post登录 jsessionid 以及cookie 传递
先配置登录接口请求 登录成功后: 再其它请求中设置
- scrapy爬取用户信息 ---崔志才
这个实例还是值得多次看的 其流程图如下,还是有一点绕的. 总结: 1 Requst(rul=' xxx ',callback= ' '),仅仅发起 某个网页 的访问请求,没啥了.剩下的交给回调函数 2 ...
- python爬虫+使用cookie登录豆瓣
2017-10-09 19:06:22 版权声明:本文为博主原创文章,未经博主允许不得转载. 前言: 先获得cookie,然后自动登录豆瓣和新浪微博 系统环境: 64位win10系统,同时装pytho ...
- CORS跨域、Cookie传递SessionID实现单点登录后的权限认证的移动端兼容性测试报告
简述 本文仅记录如标题所述场景的测试所得,由于场景有些特殊,且并不需兼容所有浏览器,所以本文的内容对读者也许并无作用,仅为记录. 场景.与实现 需在移动端单点登录 需在移动端跨域访问我们的服务 基于历 ...
- soupUI解决md5加密签名,cookie传递
问题详情: 1.接口调用需要前提状态:登录状态(cookie) 2.接口请求需要签名,签名规则为:MD5(TokenKey+apikey+timestamp+nonc) 其中 1.TokenKey.a ...
- 【JAVAWEB学习笔记】24_filter实现自动登录和解决全局的编码问题
过滤器Filter 学习目标 案例-自动登录 案例-解决全局的编码 一.过滤器Filter 1.filter的简介 filter是对客户端访问资源的过滤,符合条件放行,不符合条件不放行,并且可以对目标 ...
- Python爬虫从入门到放弃(二十四)之 Scrapy登录知乎
因为现在很多网站为了限制爬虫,设置了为只有登录才能看更多的内容,不登录只能看到部分内容,这也是一种反爬虫的手段,所以这个文章通过模拟登录知乎来作为例子,演示如何通过scrapy登录知乎 在通过scra ...
- 11.Scrapy登录
Request Request 部分源码: # 部分代码 class Request(object_ref): def __init__(self, url, callback=None, metho ...
- Python之爬虫(二十六) Scrapy登录知乎
因为现在很多网站为了限制爬虫,设置了为只有登录才能看更多的内容,不登录只能看到部分内容,这也是一种反爬虫的手段,所以这个文章通过模拟登录知乎来作为例子,演示如何通过scrapy登录知乎 在通过scra ...
随机推荐
- 协同开发中SVN的使用建议
协同开发中SVN的使用建议 1. 注意个人账户密码安全 各员工需牢记各自的账户和密码,不得向他人透漏,严禁使用他人账户进行SVN各项操作(主要考虑每个SVN账号的使用者的权限范围问题).如有忘记,请 ...
- mysql - 其它
1.mysql查看表字段和字段描述 SELECT column_name, column_comment FROM information_schema.columns WHERE table_sch ...
- linux默认网关的设置
linux装系统设IP,这应该是系统管理员的基本功,可是不同的网络结构有不同的ip设法,您知道吗? 1.一块网卡的情况 这个没啥好说的,估计地球人都知道:address,netmask,gatew ...
- DirectX12 Samples 学习笔记 – PredicationQueries
一.效果 这是一个比较简单的sample,运行sample可以看到,当红橙色长方形完全覆盖白色正方形时,白色正方形不显示,其他情况,均显示白色正方形. 二.实现 Render主要由三个部分组成 1.F ...
- angularjs(一)基础概念
一.前言 前端技术的发展是如此之快,各种优秀技术.优秀框架的出现简直让人目不暇接,作为一名业界新秀,紧跟时代潮流,学习掌握新知识自然是不敢怠慢.当听到AngularJs这个名字并知道是google在维 ...
- [转]保护眼睛的Windows和IE、Firefox、谷歌等浏览器颜色设置
保护眼睛的Windows和IE.Firefox.谷歌等浏览器颜色设置 长时间在电脑前工作,窗口和网页上的白色十分刺眼,眼睛很容易疲劳,也容易引起头痛,其实我们可以通过设置Windows窗口和软件的颜 ...
- java 常见dos命令
盘符: 进入指定的盘符下. dir : 列出当前目录下的文件以及文件夹 md : 创建目录 rd : 删除目录 注意:rd不能删除非空的文件夹,而且只能用于删除文件夹. cd : 进入指定目录 ...
- 驱动开发学习笔记. 0.05 linux 2.6 platform device register 平台设备注册 2/2 共2篇
驱动开发读书笔记. 0.05 linux 2.6 platform device register 平台设备注册 2/2 共2篇 下面这段摘自 linux源码里面的文档 : 内核版本2.6.22Doc ...
- 使用NPOI2.1.3.1版本导出word附带表格和图片
原文:http://www.cnblogs.com/afutureBoss/p/4074397.html?utm_source=tuicool&utm_medium=referral 最近项目 ...
- iOS开发】canOpenURLl 和修改http请求
控制台输出 如图是在我启动一个 Xcode + iOS 的 App 之后,控制台的输出. 这在 Xcode 时,是不会有的情况,原因是[为了强制增强数据访问安全, iOS9 默认会把所有从NSURLC ...