论如何使用scrapy框架登陆知乎

来源:互联网 发布:mysql删除数据库delect 编辑:程序博客网 时间:2024/06/10 22:09

写在前面

事情是这样的,前面几天报了一个慕课网的爬虫课程,教授使用python来编写爬虫,由于之前有使用过nodejs写爬虫的经历,所以对上手python的scrapy框架也不是那么生疏,反正思想是差不多的嘛,就是使用框架使爬虫的开发更加地有结构性,比如说下载链接的获取都是写在一个独立的.py文件中,而对获取的数据的处理,比如获取图片的url,将数据存入数据库等等,都是在专门的pipeline.py中,而对存储数据结构以及对相应数据的获取方式都写在一个item.py中,对爬虫框架整体的配置,比如数据库的配置信息,pipeline.py中处理数据的顺序,都是在setting.py中完成的,而对于页面的http请求,图片的下载,各种文件方法执行的调度,都是交给scrapy框架来完成,这让开发人员专注于爬虫的逻辑开发,这是十分高效的


问题起因

由于视频并不是最新录制,可能知乎也不想自己的网站像试验品一样被爬来爬去的,所以对登陆增加了难度,pc版的是需要点击倒立汉字,而手机版的是动态加载验证码,这对于纯请求html页面的爬虫登陆是可以做到有效防治的,虽然后面的课程有讲如何在scrpay中集成一些自动化测试平台,但是毕竟我还没看到嘛,而且要一步一个脚印,做到不跳视频,所以就卡在这里了…..


对登陆的理解

在浏览器登陆成功后,服务器会返回一段cookie保存在本地,这样当你再次登陆这个网站时,浏览器将这个cookie包含在http请求中发给服务器,服务器在确认这个cookie没过期后就会让你保持登陆状态,不需要输密码再次登陆了,cookie会在一段时间内失效,失效后就需要重新登陆了


一个想法

既然我无法做到通过scrapy登陆,那我能否可以获取那段cookie,然后让其包含在scrapy发出的http请求中,这样岂不就算是登陆了嘛

在浏览器中输入知乎网址 ’ www.zhihu.com’,登陆完成后关闭页面,再次打开时打开开发者工具,查看获取页面时浏览器发出的http的request请求, 如下:

这里写图片描述

中间有cookie一项,这就是我要的东西!!

先使用一个简单的request第三方包做了一个测试,将cookie包含在请求的header中,结果成功,证明可以

import requests, osheaders = {      'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36'# user-agent使用手机版的似乎可以减少被抓的概率           'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',            'accept-encoding': 'gzip, deflate, br',            'accept-language': 'en,zh-CN;q=0.8,zh;q=0.6,zh-TW;q=0.4',            'connection': 'keep-alive',            'cookie':'aliyungf_tc=AQAAANGG6Xr6fgsARUOVJJhV/Q1zeFk0; .....'}# z这里cookie太长了就不全粘出来了filepath = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'result.html')r = requests.get('https://www.zhihu.com/', headers=headers)f = open(filepath, 'w')print(r.status_code)f.write(r.text)f.close()print('finish') 

这是我得到到页面

这里写图片描述

虽然无法运行js脚本来动态获取更多数据,但是登陆到目的还是达到了,而且cookie的有效时间为一个月。


遇到问题

那么来到scrapy框架中也不是一样嘛,header还是之前的那个header,在主文件中把start_requests方法重载一下应该就没问题了

def start_requests(self):     return [scrapy.Request(headers=headers, url="https://www.zhihu.com"]

但是运行结果还是未登陆的页面,wfc!!!

查询scrapy官方文档,说是scrapy.Request有一个cookies属性要单独设置,而且还要求为dict类型的,如果不设置就为空dict

这里写图片描述

复习一下dict类型

    dict_example = {'p1': 's1', 'p2': 's2'}

再观察header中cookie的结构,如下

    p1=s1; p2=s2; p3="s3=z3"

问题就是结构大大的不同,需要做格式转化,还有就是字符串转dict的问题


问题解决

解决问题一有几种方案

第一种就是手动书写dict数据,虽然花不了多久时间但是这样太不优雅了,而且里面除了一个cap_id我知道是登陆后动态生成的一个id之外别的我不知道会出现什么变化,要是登陆不成功还要再次手动登陆来更新cookie数据,那还要再一个数据一个数据输入一遍,太麻烦了,我希望的是直接全部复制粘贴,然后让程序自己格式化

第二种就是通过正则替换一步到位,把每个单词之间的等号替换成冒号,把分号替换成逗号,并为每个单词都加上引号。但是我明显高估了我自己的能力了,废了大半天的时间整出了一个看似完美的正则替换式,但是没注意到还有p3="s3=z3" 这样的格式,所以最终还是任命放弃,选择了第三种方案

第三种方案代码包装成一个函数如下

import re, astdef util_cookie(arg):    __string = arg    regex1 = r'(\s)'    __string = re.sub(regex1, r'', __string)    list_string = __string.split(';')    regex2 = r'(=)'    def convert_fun(tmp_str):        result_tmp = re.sub(regex2, ':', tmp_str, 1)        process_str = result_tmp.split(":")        result_tmp = ':'.join(list(map(lambda x: "'" + x + "'", process_str)))        return result_tmp    result = list(map(convert_fun, list_string))    result_string = ''    for v in result:        result_string += v + ','    return ast.literal_eval('{' + result_string + '}')
  • 1.先将字符串中所有的空格去掉
  • 2.将字符串以’;’为间隙切割成一个list
  • 3.对于每一个子字符串,将一个出现的’=’替换为’:‘,随后以冒号为间隔切割子字符串,成为一个list,再将list中每一个孙字符串开头街尾都加上引号,再用’:’连接在一起成为新的子字符串
  • 4.将处理完的每一个子字符串以‘,’链接在一起,生成新的字符串,也就是上面代码的result_string,再在它的开头与结尾处加上大括号,就完全符合dict格式的

解决问题二的方案

这个就相对简单了,因为本来一个python文件就是以字符串的形式处理的嘛,python的标准库ast, 使用其中的literal_eval方法就行了

解决了以上两个问题之后,重写 scrapy.Request 如下

scrapy.Request(headers=headers, url='https://www.zhihu.com', cookies=util_cookie(headers['cookie']))

达到了与之前一样的效果


总结

如果使用方法二我觉得也是可以达成的,但可能真的是我太菜了,我废了大半天(真的大半天!!)的时间写的正则表达式虽然没有达到目标但对于我来说还是有所收获的,毕竟我今年暑假之前对于正则表达式还是一窍不通的,后面也许会写一篇总结正则表达式的笔记吧

原创粉丝点击