前一阵子完成了第一阶段Python基础知识的学习,从最近开始学习使用Scrapy框架抓数据。不得不说学习进度十分缓慢啊啊啊,王者农药真害(shang)人(yin)。顺便吐槽下,买了一堆书没有看,真是买书如山倒,看书如抽丝,不过最近确实花在研究Scrapy的时间很多。废话不多说,下面切回正题。
学习过程中有一些参考文章,先贴出来以示尊重:
【CSDN】用scrapy 模拟知乎的登录过程
【GitHub】fuck-login/zhihu
创建项目
在命令行中切换到要创建scrapy项目的目录,并使用以下命令:
scrapy startproject zhihu_login
之后进入项目的根目录(scrapy.cfg文件所在的目录),使用以下命令创建spider:
scrapy genspider zhihu zhihu.com
其中,zhihu为spider的名字,zhihu.com为spider的allow_domain和start_urls。
项目目录类似如下所示结构:
tutorial/
scrapy.cfg # deploy configuration file
tutorial/ # project's Python module, you'll import your code from here
__init__.py
items.py # project items definition file
pipelines.py # project pipelines file
settings.py # project settings file
spiders/ # a directory where you'll later put your spiders
__init__.py
至此,项目创建完成,接下来就是实现代码细节了。
spider大致工作流程
- 由start_requests发起第一个request,如果不重写start_requests的话,请求的是start_urls中的URL,回调函数是parse,如果重写start_requests,那么请求的URL和回调函数都可以自定义;
- 回调函数里,可以继续发起请求,也可以对爬取页面内容,比如可以爬取下一个URL;
- 最后就是把爬到的内容持久化(存储)就万事大吉了,winner winner。
详细可以参考【英文】Scrapy文档,中文文档可自行百度。
此时,尝试以下命令:
scrapy crawl zhihu
得到如下结果:
2017-09-08 14:44:06 [scrapy.core.engine] DEBUG: Crawled (500) <GET http://zhihu.com/> (referer: None)
2017-09-08 14:44:06 [scrapy.spidermiddlewares.httperror] INFO: Ignoring response <500 http://zhihu.com/>: HTTP status code is not handled or not allowed
参考文章中说这是因为未设置UA,在settings.py文件下,取消DEFAULT_REQUEST_HEADERS的注释并添加UA属性即可。
DEFAULT_REQUEST_HEADERS = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'en',
'User-Agent' : 'Mozilla/5.0 (Linux; Android 5.1.1; Nexus 6 Build/LYZ28E) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Mobile Safari/537.36'
}
执行crawl命令把页面下载下来了,需要分析知乎的登录接口以及提交的参数。
回忆下登录知乎的过程,在页面输入帐号、密码(有时候还有验证码),点击登录按钮,发生错误会有提示,正确则跳转到首页。先随便输入看看ajax请求:
我在这里用的邮箱,登录接口为:https://www.zhihu.com/login/email,同时表单提交了四项数据,email和password很好理解,captcha为验证码,_xsrf在页面中有:
再观察下验证码的请求接口:
参数r是当前时间的时间戳。
齐活儿,由此分析可知:
- 请求登录页面,从页面提取_xsrf;
- 请求验证码并下载,人工查看并输入;
- 构造登录请求
完整spider代码如下:
# -*- coding: utf-8 -*-
import scrapy, json, time, os, requests
from scrapy import FormRequest, Request
try:
from PIL import Image
except:
pass
class ZhihuSpider(scrapy.Spider):
name = 'zhihu'
allowed_domains = ['zhihu.com']
start_urls = ['https://www.zhihu.com/signin?next=/']
headers = {
'Host':'www.zhihu.com',
'Referer':'https://www.zhihu.com/',
'User-Agent':'Mozilla/5.0 (Linux; Android 5.1.1; Nexus 6 Build/LYZ28E) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Mobile Safari/537.36',
}
#使用验证码登录
def captcha_login(self, response):
with open('captcha.jpg', 'wb') as f:
f.write(response.body)
try:
im = Image.open('captcha.jpg')
im.show()
im.close()
except:
print(u'请到 %s 目录找到captcha.jpg 手动输入' % os.path.abspath('captcha.jpg'))
captcha = input('请输入验证码\n:')
formdata = response.meta['formdata']
formdata['captcha'] = captcha
yield FormRequest(url='https://www.zhihu.com/login/email', formdata=formdata, headers=self.headers, meta={'formdata':formdata}, callback=self.check_login)
#构造参数,不使用验证码直接登录
def parse(self, response):
#xsrf = response.css('[name=_xsrf]::attr(value)').extract_first()
xsrf = response.xpath("//input[@name='_xsrf']/@value").extract_first()
self.headers['X-Xsrftoken'] = xsrf
self.headers['X-Requested-With'] = 'XMLHttpRequest'
formdata = {
'_xsrf':xsrf,
'email':'jasonharnic@gmail.com',
'password':'hujunjay'
}
yield FormRequest(url='https://www.zhihu.com/login/email', formdata=formdata, headers=self.headers, meta={'formdata':formdata}, callback=self.check_login)
#检查直接登录是否成功,若失败则尝试使用验证码登录
def check_login(self, response):
formdata = response.meta['formdata']
msg = json.loads(response.text)
print(msg)
#scrapy.shell.inspect_response(response,self)
if msg['r']==1:
t = str(int(time.time() * 1000))
captcha_url = 'https://www.zhihu.com/captcha.gif?r=' + t + "&type=login&lang=en"
yield Request(url=captcha_url, headers=self.headers, meta={'formdata':formdata}, callback=self.captcha_login)
elif msg['r']==0:
yield Request(url='https://www.zhihu.com', headers=self.headers, callback=self.check_login)
中间遇到的最大的坑在于,开始的时候获取验证码并没有使用scrapy,而是使用的Request,导致一直是验证码错误,因为scrapy会处理cookoies,所以中间突然用其他方式获取,导致我获取的验证码不是我当前登录所用的。