本人刚开始学习,可能有些不对的地方,希望大家指正~~
首先,我们分析下知乎登陆的接口,打开浏览器,到知乎的登陆界面,随便输入一个账号密码,查看点击登陆它干了些什么(别输入正确的,不然他就跳到首页去啦~)
可以看到它调用了一个phone_num的接口(邮箱登陆同理就不演示了),再看下这个接口的参数
出了那个_xsrf外,其他参数根据名字大概都可以猜到了
我们先来看看这个_xsrf从哪里来的,打开网页源码,查找_xsrf,发现有一个叫_xsrf的隐藏input标签,值和我们传入的这个参数是一样的
这样,我们就可以直接去取了
除了这个_xsrf外,我们还看到有一个叫captcha的参数,我们猜测它应该就是验证码,我们为了看看这个验证码是如何获得的,刷新一下看看
发现它调了这样一个接口,我们把这个url复制下来,然后直接打开看看~发现正是我们的验证码,我们看这个验证码,他是让我们点击倒立的字,再看我们上传的参数,除了那张图片的大小外,还有一个input_points的参数,应该就是我们点击的点的坐标,服务器通过这个判断我们是否点击了倒立的字,如果我们这样传参数的话,验证码图片的大小我们看上去是固定的,但是这个坐标就比较麻烦了,我们再来分析一下这个请求验证码的url的参数,有3个参数,r,type,lang,最后一个参数好像是表示语言是中文,我们把它变为en验证一下,
发现请求变成了这样一张图片,这不就是直接输入验证码的图片了么~如果可以用这个输入验证码的话我们就可以解决验证码这个参数了
好了,请求的分析差不多就这样了~下面我们可以开始写代码了
先一下我使用的python版本为3.6
新建一个scrapy的项目,新建一个爬虫(以scrapy的base模板新建~另外,这篇文章就做登录的过程,item,pipelines,middleware,settings这些文件就用默认的就行了~)
我们想一下,这个登录的过程中,我们有两个接口需要调用,1是获取验证码,2就是登录,毫无疑问,获取验证码应该优先调用,我们把我们的初始url定为获取验证码,再看一下我们上面验证码接口的参数,有个r的参数,是一串数字,我们优先应该想到是一个和时间有关的参数,我们先用当前时间来试试,我们的入口函数start_requests就变成下面这样
def start_requests(self):
t = str(int(time.time() * 1000))
captcha_url = 'https://www.zhihu.com/captcha.gif?r=' + t + '&type=login&lang=en'
return [scrapy.Request(url=captcha_url, headers={},
callback=self.parser_captcha)]
debug 看下我们的回调函数的response
发现服务器返回了500的错误,并未进入我们的回调函数,再去看下我们的网络请求的header,我们知道scrapy对于cookie等一系列值都已经封装好了,但是下面有一个Use-Agent的参数,是需要我们传的,它表示了我们的系统信息以及我们的浏览器信息等一系列信息,我们直接在我们的浏览器复制这个,在我们的header里传进去,再次debug,进入到回调函数里了
看出body是一个二进制文件,我们把文件写入本地看看是不是我们要的验证码图片(注意:这里要用pillow的Image来把图片展示出来),发现我们的猜测都是正确的,打开就是我们的验证码图片了,然后就是读取验证码里的信息问题了,这里就用简单的打开图片我们手动输入的方式,当然,还可以用云打码这种三方平台来读取验证码信息,就可以让爬虫全程不需要人来操作了
接下来,我们就需要去调用登录的接口了,记得前面我们说的参数有个_xsrf的,我们先去取这个参数
_xsrf = response.xpath("//input[@name='_xsrf']/@value").extract_first()
参数都齐了,我们去调接口去~
def login(self, response):
xsrf = response.xpath("//input[@name='_xsrf']/@value").extract_first()
if xsrf is None:
return ''
post_url = 'https://www.zhihu.com/login/phone_num'
post_data = {
"_xsrf": xsrf,
"phone_num": 'your phone number',
"password": 'your password',
"captcha": response.meta['captcha']
}
return [scrapy.FormRequest(url=post_url, formdata=post_data, headers=self.header, callback=self.check_login)]
后面还写了个检查登录是否成功的回调
def check_login(self, response):
js = json.loads(response.text)
if 'msg' in js and js['msg'] == '登录成功':
for url in self.start_urls:
yield scrapy.Request(url=url, headers=self.header, dont_filter=True)
运行一下我们的爬虫
嗯 ,成功了,接下来就可以去主页上爬取自己感兴趣的内容了
第一次写博客,有很多东西可能描述的也不是很清楚,最后把这个爬虫的完整代码放上来吧
# -*- coding: utf-8 -*-
import json
import os
import scrapy
import time
from PIL import Image
class ZhihuloginSpider(scrapy.Spider):
name = 'zhihulogin'
allowed_domains = ['www.zhihu.com']
start_urls = ['https://www.zhihu.com/']
Agent = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36'
header = {
'User-Agent': Agent,
}
def parse(self, response):
#主页爬取的具体内容
pass
def start_requests(self):
t = str(int(time.time() * 1000))
captcha_url = 'https://www.zhihu.com/captcha.gif?r=' + t + '&type=login&lang=en'
return [scrapy.Request(url=captcha_url, headers=self.header, callback=self.parser_captcha)]
def parser_captcha(self, response):
with open('captcha.jpg', 'wb') as f:
f.write(response.body)
f.close()
try:
im = Image.open('captcha.jpg')
im.show()
im.close()
except:
print(u'请到 %s 目录找到captcha.jpg 手动输入' % os.path.abspath('captcha.jpg'))
captcha = input("please input the captcha\n>")
return scrapy.FormRequest(url='https://www.zhihu.com/#signin', headers=self.header, callback=self.login, meta={
'captcha': captcha
})
def login(self, response):
xsrf = response.xpath("//input[@name='_xsrf']/@value").extract_first()
if xsrf is None:
return ''
post_url = 'https://www.zhihu.com/login/phone_num'
post_data = {
"_xsrf": xsrf,
"phone_num": 'your phone number',
"password": 'your password',
"captcha": response.meta['captcha']
}
return [scrapy.FormRequest(url=post_url, formdata=post_data, headers=self.header, callback=self.check_login)]
# 验证返回是否成功
def check_login(self, response):
js = json.loads(response.text)
if 'msg' in js and js['msg'] == '登录成功':
for url in self.start_urls:
yield scrapy.Request(url=url, headers=self.header, dont_filter=True)