python

闲鱼商品搜索爬虫:从签名算法到反爬机制的完整逆向与实现

2026-06-26 #python#爬虫#数据采集#数据分析#requests#DrissionPage#JSONP解析#XML解析#反爬策略#浏览器模拟#数据包监听#Cookie认证

在数据采集领域,**签名算法(sign)时间戳校验(timestamp)**是最常见的反爬门槛。
本文以闲鱼(Goofish)为例,完整讲解一个真实可用的 签名级别爬虫实现,从参数逆向到稳定运行,并逐步升级为可扩展的工程化架构。


一、前言:从“抓不到数据”开始

最初,我的目标很简单:

想抓取闲鱼搜索结果,批量获取 “Python 爬虫” 相关的商品标题、价格与卖家信息。

我照着视频教程写了个爬虫,但结果一行数据都没有。
浏览器能搜到数据,代码却返回空。

排查后发现问题出在时间戳验证。闲鱼的搜索接口使用签名机制,每次请求必须带上:

  • token:来源于浏览器 Cookie 中的 _m_h5_tk
  • t:时间戳
  • sign:由 token、时间戳、appKey、请求参数计算出的 MD5 值

签名算法看似简单,但其中的 时间戳偏差问题 是关键。


二、时间戳反推:16 小时的谜团

抓包时我发现,浏览器发出的请求中:

1
t = 1759801941772

对应的实际时间是 2025-10-06 17:52:21
而我运行代码时系统时间是 2025-10-07 09:53

时间差整整 16 小时

当我强行把 time.time() 生成的时间戳往前拨 16 小时后,数据立即返回。
这说明签名算法与真实系统时区不同,闲鱼接口使用的时间戳不是本地时间,而更接近 UTC 时区

于是我将时间戳修正:

1
2
3
current_timestamp = int(time.time() * 1000)
sixteen_hours_ago = current_timestamp - (16 * 60 * 60 * 1000)
j = str(sixteen_hours_ago)

成功拿到数据。


三、签名算法复现

浏览器请求中的签名计算方式如下:

1
sign = MD5(token + "&" + timestamp + "&" + appKey + "&" + data)

其中:

  • token:Cookie 中 _m_h5_tk 的前半段
  • timestamp:时间戳(毫秒)
  • appKey:固定为 34839810
  • data:请求体 JSON 字符串

Python 实现非常直接:

1
2
string = token + "&" + j + "&" + h + "&" + c_data
sign = hashlib.md5(string.encode('utf-8')).hexdigest()

通过这一行,你完成了与前端 JS 一致的签名生成逻辑。


四、完整爬虫实现

整合签名生成、请求与数据落地,形成完整采集脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import csv, time, hashlib, requests

def get_sign(page_number):
token = '9a7fd05c49b25d5075fab51a234be307'
current_timestamp = int(time.time() * 1000)
sixteen_hours_ago = current_timestamp - (16 * 60 * 60 * 1000)
j = str(sixteen_hours_ago)
h = '34839810'
c_data = '{"pageNumber":%d,"keyword":"python爬虫","rowsPerPage":30}' % page_number
string = token + "&" + j + "&" + h + "&" + c_data
sign = hashlib.md5(string.encode('utf-8')).hexdigest()
return j, c_data, sign

headers = {
"User-Agent": "Mozilla/5.0 ...",
"Referer": "https://www.goofish.com/",
"Cookie": "t=xxx; _m_h5_tk=9a7fd05c49b25d5075fab51a234be307_1759808103035; ..."
}

f = open('商品搜索爬虫批量.csv', 'w', newline='', encoding='utf-8')
writer = csv.DictWriter(f, fieldnames=['标题','区域','昵称','标签','产品简介','价格','标题详情'])
writer.writeheader()

for page in range(1, 8):
print(f'正在采集第{page}页...')
j, c_data, sign = get_sign(page)
params = {"jsv": "2.7.2", "appKey": "34839810", "t": j, "sign": sign, "v": "1.0"}
data = {'data': c_data}
res = requests.post("https://h5api.m.goofish.com/h5/mtop.taobao.idlemtopsearch.pc.search/1.0/",
headers=headers, params=params, data=data)
json_data = res.json()
for item in json_data['data']['resultList']:
main = item['data']['item']['main']
dit = {
'标题': main['exContent']['detailParams']['title'],
'区域': main['exContent']['area'],
'昵称': main['exContent']['userNickName'],
'标签': main['clickParam']['args']['tagname'],
'产品简介': main['exContent']['title'],
'价格': main['clickParam']['args']['price'],
'标题详情': main['exContent']['title']
}
print(dit)
writer.writerow(dit)

运行结果:
脚本能成功批量获取闲鱼搜索结果,自动写入 商品搜索爬虫批量.csv


五、反爬机制与规避策略

闲鱼接口有严格的防御逻辑,以下几点需要重点注意:

风控点 描述 应对方案
Cookie 绑定 _m_h5_tk 与 token 关联 定期更新 Cookie,自动解析 token
时间窗口 验签允许 ±1 小时偏差 建议提取 _m_h5_tk 内时间戳
请求频率 高频请求封禁 IP 加入随机延时:time.sleep(random.uniform(1.2,3.6))
Referer 校验 必须来源 goofish.com 固定 Referer 头保持一致性

六、完整的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
import csv
import time
from pprint import pprint

import requests
import hashlib


def get_sign(page_number):
"""
生成请求签名和相关参数

该函数根据页面编号生成用于闲鱼搜索接口的签名和时间戳参数。
通过特定算法生成签名,确保请求能够通过接口验证。

Args:
page_number (int): 需要请求的搜索结果页码

Returns:
tuple: 包含三个元素的元组
- str: 时间戳字符串
- str: 请求数据的JSON字符串
- str: 生成的MD5签名
"""
token = '9a7fd05c49b25d5075fab51a234be307'
'''
# 原本我也是按照视频里的代码运行但是失败了没有数据过来,于是我就不断比对视频里的代码 和
# 我实际上的 数据 以及浏览器请求的参数 最终发现 浏览器请求出去的时间戳和我当前的事件和日期 做出来的时间戳 完全不一致 问过AI 发现两个时间戳 的 差值高达16个小时
抓包时间与当前时间的差异:
1759801941772 对应的时间是 2025-10-06 17:52:21.772
当前系统时间是 2025-10-07 09:53(左右)
这说明你是在大约 16 小时前进行的抓包操作
于是 我就将当前时间戳向后倒了 16个小时果然 数据直接过来了
'''
current_timestamp = int(time.time() * 1000)
print(f"当前时间戳: {current_timestamp}")
sixteen_hours_ago = current_timestamp - (16 * 60 * 60 * 1000)
# j = str(int(time.time() * 1000))
j = str(sixteen_hours_ago)
h = '34839810'
c_data = '{"pageNumber":%d,"keyword":"python爬虫","fromFilter":false,"rowsPerPage":30,"sortValue":"","sortField":"","customDistance":"","gps":"","propValueStr":{},"customGps":"","searchReqFromPage":"pcSearch","extraFilterValue":"{}","userPositionJson":"{}"}' % page_number
string = token + "&" + j + "&" + h + "&" + c_data
# print(string)
sign = hashlib.md5(string.encode('utf-8')).hexdigest()

return j, c_data, sign


# 打开CSV文件准备写入数据
f = open('商品搜索爬虫批量.csv', mode='w', newline='', encoding='utf-8')
writer = csv.writer(f)
# 创建DictWriter对象,指定列名
csv_writer = csv.DictWriter(f, fieldnames=[
'标题',
'区域',
'昵称',
'标签',
'产品简介',
'价格',
'标题详情',
])
# 写入表头
csv_writer.writeheader()

# 设置请求头信息,包括User-Agent、Referer和Cookie等
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36",
"Referer": "https://www.goofish.com/",
"Cookie": "t=333d89bdd8d3c7f1680d61c314977359; tracknick=tb575736359; cna=5dFRIU3sMHMBASQJijx6xuFX; isg=BA4O1Z6E6rPx1V7kM_eYllnfX-TQj9KJBFr77zhXA5HMm671oBaHmbdQ18f3hsqh; cookie2=1bd479bb51a7304737dd39912bcce0eb; _samesite_flag_=true; sgcookie=E100lSj5ts%2Bd9eb0HAVpdFmW%2BsVh9FYgXvUZuwcDxh0BtMKbwlSXJGs5OlNCOdjXKrrUB4oRAn7SpNlLypY%2BSmUclm%2Fn0PN6lsfI%2FDaqY7Dmno6l%2F5%2BZX8Jlqq1URukGipG2; csg=2de4d3fd; _tb_token_=3f65e75e137d5; unb=2209968140617; sdkSilent=1759879902293; xlly_s=1; mtop_partitioned_detect=1; _m_h5_tk=9a7fd05c49b25d5075fab51a234be307_1759808103035; _m_h5_tk_enc=c4118ef3c44e5a8a979739e9dba2c653; tfstk=gBw-nFcf0ZbkVK8y2yfcKGNY-0IcssqzZzr6K20kOrUYfl8oO8DuJBUuYycCzY2LkkaO4DbzK3P4Sl9uE_kHpYkEdNbGIOmz4vkB0iYP2UJjbD6HVLgIUxO_ruBOIOqzV3m5jo6gKL3BIDuIdbGIcKgqvpOQRXgjlqoEAUiBFiEjuqiBRpOSlniixeT7RvsYcqoEVvG7RxsxYqgIdvGkzg32V0pLNHUa4cQlNCRaH0h-Jp0vpVwDIb01eq9ppqzIwQqSkp9QHWdeq235_n0EE4rq2yWwUYGLO8DbFZ6IC5zYpfMOtgkTDSViwSQWpq2ouf2SBH67DY3-1-h6vdz_DkNiM-j1QYHSPWDzxhQuD8Uu4-EHAIM-EShTeA6wP2V0XJnLLwWYWkZaOjepyglzIRHPLF0txQsADBRENmRxupw3Fgv2Bm3GVbOeTXmqDVjADBRENmoxSg9WTBln0"
}

# 循环爬取多页搜索结果数据
for pageNumber in range(1, 8):
print(f'正在采集第{pageNumber}页的数据内容')
url = "https://h5api.m.goofish.com/h5/mtop.taobao.idlemtopsearch.pc.search/1.0/"
j, c_data, sign = get_sign(pageNumber)

# 构造请求参数
params = {
"jsv": "2.7.2",
"appKey": "34839810",
"t": j,
"sign": sign,
"v": "1.0",
"type": "originaljson",
"accountSite": "xianyu",
"dataType": "json",
"timeout": "20000",
"api": "mtop.taobao.idlemtopsearch.pc.search",
"sessionOption": "AutoLoginOnly",
"spm_cnt": "a21ybx.search.0.0",
"spm_pre": "a21ybx.search.searchInput.0"
}

# 构造请求数据
data = {
'data': c_data
}

# 发送POST请求获取搜索结果
response = requests.post(url, headers=headers, params=params, data=data)
json_data = response.json()
# pprint(json_data)
# exit()
resultList = json_data['data']['resultList']
# pprint(resultList)

# 遍历搜索结果,提取并保存商品信息
for item in resultList:
try:
main = item['data']['item']['main']
# pprint(main)
fishTags_keys = main['exContent']['fishTags'].keys()
if 'r2' in fishTags_keys:
tagname = ''.join(
[i['utParams']['args']['content'] for i in main['exContent']['fishTags']['r2']['tagList']])
else:
tagname = '无'
dit = {
'标题': main['exContent']['detailParams']['title'],
'区域': main['exContent']['area'],
'昵称': main['exContent']['userNickName'],
'标签': main['clickParam']['args']['tagname'],
'产品简介': tagname,
'价格': main['clickParam']['args']['price'],
'标题详情': main['exContent']['title'],
}
print(dit)
csv_writer.writerow(dit)
# break
except Exception as e:
# 捕获其他未预期的异常并打印详细信息,但不中断程序执行
print(f"未预期的错误: {type(e).__name__}: {e}")
pass

七、结语:技术的本质是洞察

这次实战最大的收获不是拿到数据,而是理解了平台反爬体系的逻辑。
反爬的本质,不是“反你”,而是防止滥用。
而爬虫的价值,不在于突破限制,而在于理解机制、提取规律、生成洞察。

当你能看懂签名算法、识别时区错位、动态适配 Cookie——
你就已经跨越了“脚本执行者”与“系统工程师”之间的界线。


总结一句话:

爬虫不是为了“抓到数据”,而是为了“理解系统如何守护数据”。

评论
分享