Python POST 爬虫爬取掘金用户信息

本文介绍了如何使用Python的requests库通过POST方式爬取掘金网站的用户信息。通过分析网络请求,发现参数加密采用了base64编码,解码后控制翻页的参数为cursor。对五个主要技术分类进行爬取,提取作者的个人信息,去除重复数据,最终获取到16598条用户信息并存储到csv文件中。爬取过程中注意参数构造、数据提取和存储策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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"]) #id
        user_info = {} #存储提取后作者信息的字典
        user_info["id"] = info_dic["user_id"] #id
        user_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_id
    cate_password = cate_dict[key][1] #v
    for page in range(20, 40060, 40): #控制翻页
        encode_dic = {} #需要编码的cursor原始形式是一个字典
        encode_dic["v"] = cate_password
        encode_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 = 0
    print("写入成功")

爬取结果:

在这里插入图片描述

共16598条无重复的用户数据。

6. 源代码:

import requests
import base64
import pandas as pd

num = 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] #v
    for page in range(20, 40060, 40): #翻页
        time.sleep(random.random()) #休眠0-1的随机时间
        encode_dic = {} #cursor参数字典
        encode_dic["v"] = cate_password
        encode_dic["i"] = page
        #编码
        cursor = base64.b64encode(str(encode_dic).replace("'", "\"").encode())
        #print(cursor)
        p["cate_id"] = cate_id
        p["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"]) #id
                user_info = {} #存储提取后作者信息的字典
                user_info["id"] = info_dic["user_id"] #id
                user_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 #爬取页码加1
        if num == 25: #每25页存储一次
            data = pd.DataFrame(info_lst) #数据类型转化
            data.to_csv(r"juejin.csv", mode = "a") #追加写入
            info_lst = []
            num = 0
            print("写入成功")
    print("key:{}完成".format(key))
    print()
    print()
            
        
<think>好的,用户让我介绍一下这个链接的内容,但链接是https://juejin.cn/post/7475229875822559273?searchId=202503031356335F7C9B3856FFEC520B66。首先,我需要确认这个链接对应的内容是什么。不过,由于我无法直接访问外部链接,我必须依赖用户提供的信息或已有的知识。 用户之前的问题是关于OPPO厂商在Android角标适配的解决方案,现在又提到了这个链接。可能这个链接是一篇技术文章,讨论类似的主题,比如Android通知适配,特别是针对OPPO设备的角标问题。或者可能涉及更广泛的Android开发内容,比如性能优化、兼容性问题等。 接下来,我需要回顾之前的对话历史。用户之前询问了OPPO厂商的角标适配问题,我提供了详细的解决方案,包括白名单机制、版本差异、私有协议限制,以及具体的适配方案和验证流程。现在用户可能希望了解该链接中的内容是否与之前讨论的主题相关,或者是否有补充信息。 由于无法直接访问链接,我需要基于已有知识推测内容。可能这篇文章讨论了Android应用在不同厂商设备上的适配问题,特别是OPPO设备的角标显示问题,或者可能涉及其他厂商如小米、华为等的适配策略。也有可能这篇文章提供了更详细的代码示例或最新的适配方法,特别是考虑到OPPO的政策可能会频繁更新。 用户的需求可能是想确认该链接中的信息是否准确,或者希望我总结该文章的内容。但鉴于无法访问链接,我需要礼貌地告知用户这一点,同时提供进一步的帮助。比如,建议用户提供文章的关键点或具体问题,以便我能根据已有知识进行解答。 另外,用户可能对之前的回答感到满意,希望获得更多相关信息,因此转向另一个资源。需要确保回应用户时保持专业和帮助的态度,避免因无法访问链接而让用户失望。同时,可以提醒用户,如果他们有具体问题或文章中的某些内容需要解释,我可以帮助他们分析或提供更多背景知识。 总结来说,我需要告知用户无法直接访问链接,但愿意根据提供的详细信息或具体问题进行解答。同时,可以重申之前关于OPPO角标适配的关键点,以提供持续的支持。此外,保持友好和主动帮助的态度,鼓励用户提出更具体的问题,以便更好地协助他们。</think>由于我无法直接访问外部链接或查看网页内容,但根据URL结构可以判断这是掘金(juejin.cn)上的一篇技术文章。以下是基于链接参数和常见技术文章类型的推测性分析: --- ### 关于该文章的可能内容推测(仅供参考): 1. **技术类型判断** - URL中的`7475229875822559273`为文章ID - `searchId=202503031356335F7C9B3856FFEC520B66`表明用户通过搜索访问 - **可能涉及方向**: - Android系统级功能适配(如通知角标、权限管理) - 多厂商设备兼容性解决方案 - OPPO ColorOS系统特性深度解析 2. **典型技术文章结构推测**: ```markdown # [标题可能包含] - Android角标适配终极指南 - OPPO设备通知兼容性实战 - 跨厂商系统特性适配方案 ## 核心内容可能涵盖: 1. 国内主流Android厂商(华为/小米/OPPO/vivo)适配差异对比 2. OPPO私有API调用示例(如角标广播的特殊处理) 3. 最新ColorOS版本限制及绕过方案 4. 推送通道集成最佳实践 ``` --- ### 如需进一步分析,可以提供以下信息: 1. 文章中提到的关键技术点(如特定API、适配方案) 2. 遇到的具体兼容性问题描述 3. 相关代码片段或错误日志 我可以基于这些信息提供更具体的实现建议和原理分析。 (注:如果需要了解某类技术问题的通用解决方案,例如OPPO设备通知角标适配,我可以基于现有知识库给出专业指导)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值