Python POST 爬虫爬取掘金用户信息
Python POST 爬虫爬取掘金用户信息
1. 概述
Python第三方库requests提供了两种访问http网页的函数,基于GET方式的get()函数和基于POST方式的post()函数。
get函数是最为常用的爬取方式,可以获取静态HTML页面和大部分动态加载json文件,但有一些网站对部分数据文件进行了加密处理,不能使用get来获取到文件内容,此时就需要使用post函数来进行获取。例如本文中所要爬取的掘金网站的用户信息。
POST方式与GET方式最主要的区别在于POST在发送请求时会附上一部分参数,服务器根据参数来选择返回给客户的内容。因此在使用post函数时,一大重要之处在于构造参数。有一部分网站会对参数进行加密,这种时候就需要通过观察和一些常用解密方法来进行解密,常用的加密方法包括base64、Hex以及压缩等方法,本文破解的掘金网站参数是使用了base64加密。
综上,本文通过post函数构造加密参数获取了掘金五个主要分类下面的5000页文章信息并从中提取出作者的个人用户信息,去掉重复信息,共获取16598条个人用户信息。
2. 网页观察
掘金,https://juejin.cn/,国内知名极客平台、程序员社区,掘金中有非常多的技术大牛,通过获取掘金的用户信息,我们可以提取出非常多关于国内程序员现状的有价值信息。
观察掘金首页,会发现没有直接的用户展示页面,因此,要获取大量用户数据,我们拟定通过获取文章作者信息的方式来获取用户数据。掘金首页有若干个标签分类,每一个标签分类会展示若干篇文章,我们计划主要针对“后端”,“前端”,“Android”,“iOS","人工智能”五个标签分类来获取。
进入“后端”页面,下拉到尽头,发现该网页的文章加载并不是通过静态翻页的形式,而是动态加载json文件的形式。
F12检查,进入network,下拉加载新的文章:
观察加载出来的新文件,发现一个新文件“recommend_cate_feed”:
点击该文件,选择Preview板块,发现为一个字典,其中有若干个键,打开data键,发现其中包含有若干文章信息,可以确定此文件即为我们所需要的文件:
直接打开此链接网站,网页显示“404”,不能直接通过GET访问,需要使用POST方式来访问。
然后我们继续下拉窗口,加载新的文件,对比之前的文件发现,两者的链接网址都一样,不同之处在于POST发送的参数中的cursor值不同:
(原文件整体信息)
(新文件整体信息)
(原文件参数)
(新文件参数)
因此我们可以推测出控制翻页和类别标签的参数都是cursor,可以看出cursor是加密过后的值,如要使用其模拟翻页需要对其进行解密。
3. 数据提取
从上文中我们获取到了所需信息的网址位置,接下来我们准备使用Python来试爬取,并对其参数进行解密。
首先导入所需库:
import requests #用于数据获取
import pandas as pd #用于数据存储
将上文中找到的网址作为目标网址;
url = r"https://api.juejin.cn/recommend_api/v1/article/recommend_cate_feed"
先将上文查看到的文件中的所有参数都复制下来作为POST参数(Request Payload中的信息):
复制成字典类型:
p = {'id_type': 2, 'sort_type': 200, 'cate_id': '6809637769959178254', 'cursor': 'eyd2JzogJzY5ODY2NTI5MjU5NjM1MzQzNTAnLCAnaSc6IDYwfQ==', 'limit': 20}
再构造请求头,POST请求一般必备的请求头包括:user-agent, accept-encoding, accept-language, content-length和content-type。
将上述几项从Request Headers中复制成字典:
header = {"user-agent": "Mozilla/5.0", "accept-encoding": "gzip, deflate, br","accept-language": "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7","content-length": "132","content-type": "application/json"}
注意:有时读取到的可能是乱码,进行encoding转化的测试之后发现是因为请求头中支持br压缩编码,返回的编码是使用br压缩之后的,而requests默认解压缩只支持gzip。进行解压缩较为麻烦,直接将请求头中支持压缩编码的部分去掉,仅接受gzip压缩。
所以请求头构造如下:
header = {"user-agent": "Mozilla/5.0", "accept-encoding": "gzip","accept-language": "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7","content-length": "132","content-type": "application/json"}
然后使用post函数获取其内容,存储其json格式内容,打印出来观察:
r = requests.post(url, data = json.dumps(p), headers = header)
js_lst = r.json()
print(js_lst)
打印如下:
观察数据,发现我们需要的数据存储在data值之中,为一个列表,列表里每一篇文章为一个元素。再深入js_lst[“data”]观察,发现作者信息存储在author_user_info值之中,并作为字典存储了作者的诸多用户信息,将我们关心的数据提取出来:
for item in js_lst["data"]: #遍历每一篇文章info_dic = item["author_user_info"] #作者信息存储的字典if info_dic["user_id"] not in id_lst: #检查id来判断该作者信息是否已经被读取id_lst.append(info_dic["user_id"]) #iduser_info = {} #存储提取后作者信息的字典user_info["id"] = info_dic["user_id"] #iduser_info["name"] = info_dic["user_name"] #用户名user_info["company"] = info_dic["company"] #所在公司user_info["job"] = info_dic["job_title"] #职业user_info["level"] = info_dic["level"] #用户等级user_info["descrip"] = info_dic["description"] #个人简介user_info["fans"] = info_dic["followee_count"] #粉丝数user_info["stars"] = info_dic["follower_count"] #关注数user_info["articles"] = info_dic["post_article_count"] #原创文章数user_info["get_like"] = info_dic["got_digg_count"]#点赞数user_info["views"] = info_dic["got_view_count"] #浏览量info_lst.append(user_info) #将该用户的信息加入到用户列表
此处使用了将爬到的每一个用户的id都存入一个列表,在每一次爬取中先判断此时爬取的用户id是否在列表中,来避免重复爬取。
4.参数构造
接下来我们来构造参数实现多页面的爬取。
根据上文分析,POST请求时需要附带的参数共有五个:id_type, sort_type, cate_id, cursor以及limit。
根据上文对不同文件进行分析,再对不同类型的页面进行参数对比,我们得到id_type、sort_type和limit都是不需要改变的参数,直接使用上文中的即可。而cate_id控制了内容的分类(后端、前端等),cursor控制了翻页。
由于我们的目标类别只有主要的5种类别,因此我们直接获取五个类别的cate_id即可。较为麻烦的参数为cursor,可以看出其是经过加密之后的值,我们需要先将其解密。
根据cursor值的特点,我们推测其使用base64加密编码,尝试使用base64对上文的cursor进行解码:
import base64 #导入base64库
print(base64.b64decode(b'eyJ2IjogIjY5ODY2NTI5MjU5NjM1MzQzNTAiLCAiaSI6IDIwfQ==')) #打印解码之后的内容
可以看出确实是使用base64编码加密。
通过对几个不同的文件参数解码之后对比,我们可以得出结论:解码之后的v是类型值,而i是页数,i从20开始,以40为单位。
对于v,由于其与上文的类型数cate_id并不相同,我们人工获取五个目标类别的v参数,并存成字典:
cate_dict = {"后端": ["6809637769959178254","6986652925963534350"],
"前端": ["6809637767543259144","6986785259966857247"],
"安卓": ["6809635626879549454","6986154038312042504"],
"人工智能": ["6809637773935378440","6986885294276476958"],
"IOS": ["6809635626661445640", "6986425314301870094"]}
然后我们选择对应的v和i进行编码,查看对比编码是否正确。
for key in list(cate_dict.keys()): #遍历每一个类别cate_id = cate_dict[key][0] #cate_idcate_password = cate_dict[key][1] #vfor page in range(20, 40060, 40): #控制翻页encode_dic = {} #需要编码的cursor原始形式是一个字典encode_dic["v"] = cate_passwordencode_dic["i"] = page#编码cursor = base64.b64encode(str(encode_dic).replace("'", "\"").encode()) #j将该字典转化为字符串并将其中的单引号转化为双引号,再编码成字节类型,再使用base64编码print(cate_id)print(cursor.decode()) #打印编码出来的结果,使用字符串显示
编码结果如下:
注意此处涉及到的一个小技巧。因为Python同时支持单引号和双引号,且默认为单引号,所以将字典转化为字符串之后,字典中的项使用单引号。但正确参数解码之后的字典为双引号,因此需要将字符串之中的单引号使用replace函数转化为双引号,否则编码出来的结果不正确
从而我们就构造好了所需的参数。
5. 结果存储
构造好参数,提取好数据之后,我们就可以进行大批量爬取了。由于是爬取的动态API接口,因此一般不会因为爬取速度过快被检测到,不需要进行休眠。为保证质量,也可以适当进行休眠。
最后涉及到的一个问题是爬取到的数据的存储,因为爬取到的数据量较大,爬取耗时较长,最好是多次写入到csv文件中,从而保证即使在爬取过程中出现网络错误也不至于需要全部重来。
存储数据本文使用pandas库进行存储,将爬取到的列表转化为pandas的DataFrame类型,然后使用to_csv函数即可写入。
data = pd.DataFrame(info_lst) #数据类型转化
data.to_csv(r"juejin.csv")
为实现分批写入,我们在爬取过程中维护一个num变量,每获取一页num加1,当num等于25时将info_lst中的内容写入到文件中,并将info_lst清空以避免造成太大的内存消耗。如此需要使用追加写入,否则每一次写入会清空原有内容,追加写入只需设置mode为a。
if num == 25: #已爬取25页data = pd.DataFrame(info_lst) #数据类型转化data.to_csv(r"juejin.csv", mode = "a") #追加写入info_lst = [] #清空暂存列表num = 0print("写入成功")
爬取结果:
共16598条无重复的用户数据。
6. 源代码:
import requests
import base64
import pandas as pdnum = 0
url = r"https://api.juejin.cn/recommend_api/v1/article/recommend_cate_feed"
p ={"id_type":2,"sort_type":200,"cate_id":"6809637769959178254","cursor":"eyJ2IjoiNjk4NjY1MjkyNTk2MzUzNDM1MCIsImkiOjQwMDIwfQ==","limit":20}
header = {"user-agent": "Mozilla/5.0","cookie":"MONITOR_WEB_ID=6df3866a-2cab-4818-bb57-e75e971da3f8; _ga=GA1.2.1480397537.1626829211; _gid=GA1.2.1556542559.1626829211","accept-encoding": "gzip","accept-language": "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7","content-length": "132","content-type": "application/json"
}
id_lst = [] #去重所用的id列表
info_lst = [] #暂存结果列表for key in list(cate_dict.keys()): #遍历每一个分类cate_id = cate_dict[key][0] #类别号cate_password = cate_dict[key][1] #vfor page in range(20, 40060, 40): #翻页time.sleep(random.random()) #休眠0-1的随机时间encode_dic = {} #cursor参数字典encode_dic["v"] = cate_passwordencode_dic["i"] = page#编码cursor = base64.b64encode(str(encode_dic).replace("'", "\"").encode())#print(cursor)p["cate_id"] = cate_idp["cursor"] = cursor.decode()#print(p)r = requests.post(url, data = json.dumps(p), headers = header)#print(r.apparent_encoding)r.encoding = "utf-8"#print(r.text)js_lst = r.json()for item in js_lst["data"]: #遍历每一篇文章info_dic = item["author_user_info"] #作者信息存储的字典if info_dic["user_id"] not in id_lst: #检查id来判断该作者信息是否已经被读取id_lst.append(info_dic["user_id"]) #iduser_info = {} #存储提取后作者信息的字典user_info["id"] = info_dic["user_id"] #iduser_info["name"] = info_dic["user_name"] #用户名user_info["company"] = info_dic["company"] #所在公司user_info["job"] = info_dic["job_title"] #职业user_info["level"] = info_dic["level"] #用户等级user_info["descrip"] = info_dic["description"] #个人简介user_info["fans"] = info_dic["followee_count"] #粉丝数user_info["stars"] = info_dic["follower_count"] #关注数user_info["articles"] = info_dic["post_article_count"] #原创文章数user_info["get_like"] = info_dic["got_digg_count"]#点赞数user_info["views"] = info_dic["got_view_count"] #浏览量info_lst.append(user_info) #将该用户的信息加入到用户列表print("完成第{}/{}页".format(int((page-20)/40), 1000))num+=1 #爬取页码加1if num == 25: #每25页存储一次data = pd.DataFrame(info_lst) #数据类型转化data.to_csv(r"juejin.csv", mode = "a") #追加写入info_lst = []num = 0print("写入成功")print("key:{}完成".format(key))print()print()
Python POST 爬虫爬取掘金用户信息相关推荐
- [Python 爬虫] 使用 Scrapy 爬取新浪微博用户信息(四) —— 应对反爬技术(选取 User-Agent、添加 IP代理池以及Cookies池 )
上一篇:[Python 爬虫] 使用 Scrapy 爬取新浪微博用户信息(三) -- 数据的持久化--使用MongoDB存储爬取的数据 最近项目有些忙,很多需求紧急上线,所以一直没能完善< 使用 ...
- [Python 爬虫] 使用 Scrapy 爬取新浪微博用户信息(二) —— 编写一个基本的 Spider 爬取微博用户信息
上一篇:[Python 爬虫] 使用 Scrapy 爬取新浪微博用户信息(一) -- 新建爬虫项目 在上一篇我们新建了一个 sina_scrapy 的项目,这一节我们开始正式编写爬虫的代码. 选择目标 ...
- [Python 爬虫] 使用 Scrapy 爬取新浪微博用户信息(三) —— 数据的持久化——使用MongoDB存储爬取的数据
上一篇:[Python 爬虫] 使用 Scrapy 爬取新浪微博用户信息(二) -- 编写一个基本的 Spider 爬取微博用户信息 在上一篇博客中,我们已经新建了一个爬虫应用,并简单实现了爬取一位微 ...
- python微博爬虫实战_爬虫实战(一):爬取微博用户信息
前言 最近做课设,是一个有关个人隐私安全的课题,在网上找了很多论文,最后上海交通大学的一篇硕士论文<面向社会工程学的SNS分析和挖掘>[1] 给了我很多灵感,因为是对个人隐私安全进行评估, ...
- python爬取微博用户信息(六)—— 完整代码
本节为爬取微博用户信息的完整代码,以及项目结构. 感兴趣的小伙伴可以收藏哦! 另外,关于本代码的效果展示,以及教程,点击以下链接即可. python爬取微博用户信息(一)-- 效果展示 python爬 ...
- python爬取微博用户信息(四)—— 爬取用户信息的功能函数
这一节,将会将 MicroBlog.py 文件中的函数介绍一下. 感兴趣的小伙伴可以收藏哦! 另外,关于本代码的效果展示,以及教程,点击以下链接即可. python爬取微博用户信息(一)-- 效果展示 ...
- python爬取微博用户信息(三)—— 创建MicroBlog类实例
这一节,主要讲述 main.py文件,该文件创建了一个MicroBlog类,MicroBlog类中包含一些爬取微博内容的函数. 以及简单介绍traceback的用法. 感兴趣的小伙伴可以收藏哦! 另外 ...
- 深圳python爬虫培训南山科技园钽电容回收_记一次python 爬虫爬取深圳租房信息的过程及遇到的问题...
为了分析深圳市所有长租.短租公寓的信息,爬取了某租房公寓网站上深圳区域所有在租公寓信息,以下记录了爬取过程以及爬取过程中遇到的问题: 爬取代码: import requests from reques ...
- python爬取微博用户信息(一)—— 效果展示
有时,我们想要了解一个人,一般会通过ta的微博.朋友圈以及朋友的看法去了解. 特别是面对自己喜欢的明星,我们常常会浏览ta的微博,了解ta的动态. 那么,在大数据时代,数据已经成为了不可缺少的一部分, ...
最新文章
- 使用 Vue + Flask 搭建单页应用
- 浅谈MAXIMO项目实施(转)
- Docker使用-构建MySQL
- E06-libcrypto.so.10 遗失的解决办法
- ios 旋转屏幕试图切换_TCL·XESS 旋转智屏 A200Pro 评测:方向一换,体验大不相同...
- 开源的 DNS 转发软件 Dnsmasq 被曝7个漏洞,可劫持数百万台设备
- docker-containerd 启动流程分析
- 中国古代亲属称谓家谱总览
- flex:1代表什么
- java游戏猿人时代_猿人时代_JAVA游戏免费版下载_7723手机游戏[www.7723.cn]
- Eclipse Mars2在线安装svn详细步骤
- 数列和级数(基本概念)
- 高效领导者提升思考力的5个底层法则
- 知乎热议:27岁没文凭、想自学编程,有机会成为程序员吗?互联网风口一去不复返了吗?
- ios播放视频demo
- 正向代理和反向代理区别
- 根据物理公式在Unity中实现抛物线运动.1
- 洛谷刷题笔记 求一元二次方程
- python正确读音_8个正确使用Python的迹象
- HP电脑恢复系统(一键还原)
热门文章
- 如何改善C++代码项目的构建效率,降低构建时间?
- Axure RP实例教程:组合弹出菜单效果
- 特别策划 | 5G最新进展深度解析2020版-国内市场篇(56页附下载)
- 常用代码模板3——搜索与图论(Bellman-Ford算法 、spfa 算法、floyd算法、Kruskal算法、染色法、匈牙利算法 )
- c/c++:Libevent应用(Libevent介绍、 事件处理框架 - event_base、事件循环、事件、带缓冲区的事件、链接监听器)
- 百度贴吧发贴机编写教程
- web的一些基本名词概念
- 云时代架构之游戏服务器的架构演进
- 提高数据科学家讲故事能力的5个小技巧
- 量子计算机到底有多快?,量子计算机运算到底有多快?