资讯详情

司法拍卖土地数据爬虫:应对反爬、结构突变与双平台适配

📅 2026/10/11 7:57:05 | 华诺云谱 👁 阅读
司法拍卖土地数据爬虫:应对反爬、结构突变与双平台适配
简介本资源是一套面向Python初学者与数据采集实践者的司法拍卖信息自动化采集工具聚焦淘宝、京东两大平台的土地类司法拍卖每日数据抓取需求适用于潜在竞买人、法律从业者及市场研究人员快速获取标的物名称、位置、起拍价、保证金、拍卖时间等关键字段。压缩包共3个文件含2个核心爬虫脚本taobaofapai.py、sf_jd.py分别实现淘宝与京东司法拍卖土地页的结构化解析1个README.md提供项目说明与基础使用指引整体仅6KB轻量易部署无冗余依赖。目前已有793人学习下载适合希望理解电商司法拍卖页面反爬应对逻辑、掌握动态请求模拟与HTML数据定位技巧的学习者。读者可直接复用脚本框架结合requestsBeautifulSoup完成基础数据采集并依据注释快速适配登录验证、分页翻页与本地CSV存储等常见扩展功能。1. 这不是通用爬虫脚本而是一套专为司法拍卖土地信息设计的Python数据采集方案解决字段缺失、反爬跳转、页面结构突变三大顽疾你有没有试过用常规 Requests BeautifulSoup 爬淘宝/京东司法拍卖频道刚跑通半小时第二天就返回空列表——不是代码错了是页面里「标的物类型」字段突然从span换成div>from requests_html import HTMLSession def fetch_page_with_js(url: str) - str: session HTMLSession() try: r session.get(url, timeout15) # 关键只等待关键元素出现而非整页加载完成 r.html.render(wait2, timeout20, scrolldown1) return r.html.html except Exception as e: print(f预加载失败 {url}: {e}) return finally: session.close() # 必须显式关闭否则进程残留提示scrolldown1参数非可选——京东司法页的「标的详情」按钮默认折叠需滚动触发展开逻辑否则后续找不到 DOM 节点。2.2 结构识别层用 CSS 选择器指纹库应对页面结构突变淘宝/京东司法频道每月平均发生 2.3 次 DOM 结构微调如 class 名追加哈希值、标签层级增减。硬编码soup.find(div, class_price-box)必然失效。本方案构建了「CSS 选择器指纹库」对每个关键字段预存 3~5 条备选选择器按匹配成功率排序。例如「起拍价」字段平台主选器备选器1备选器2触发条件淘宝div.price-box span:nth-child(2)div[data-v-abc123] .pricescript:contains(__INITIAL_STATE__)主选器匹配为空时启用京东div.detail-price .numdiv.product-price .pricediv#detail-data script同上该指纹库以 JSON 文件形式存于config/selectors.json运行时动态加载无需改代码即可热更新。2.3 字段提取层正则 JSON 解析双轨制覆盖所有数据埋点方式土地类标的字段来源极杂静态 HTML 文本如「土地用途住宅用地」→ 正则r土地用途(.?)\s*内联 JSON 变量淘宝的window.__INITIAL_STATE__ { ... }→json.loads(re.search(r__INITIAL_STATE__ ({.*?});, html).group(1))AJAX 接口响应京东的/api/detail?idxxx→ 单独请求并解析 JSON提取函数统一返回dict字段名标准化如land_use,appraisal_price,guarantee_amount屏蔽平台差异import re import json def extract_land_fields(html: str, platform: str) - dict: fields {} # 方式1正则提取文本兜底 if m : re.search(r土地用途[:]\s*(.?)\s*(?:||。|$), html): fields[land_use] m.group(1).strip() # 方式2解析淘宝内联JSON if platform taobao: if m : re.search(r__INITIAL_STATE__ ({.*?});, html, re.DOTALL): try: data json.loads(m.group(1)) # 从嵌套JSON路径中取值路径已预存在 config/paths.json fields[land_use] data.get(auction, {}).get(item, {}).get(landUse, ) fields[appraisal_price] float(data.get(auction, {}).get(item, {}).get(appraisalPrice, 0)) except (json.JSONDecodeError, KeyError, ValueError): pass # 方式3京东API补全需构造请求头 if platform jd and land_use not in fields: detail_api_url fhttps://item.jd.com/api/detail?id{extract_jd_id(html)} # ... 请求并解析 ... return fields参数说明extract_jd_id()是从京东 URL 或 HTML 中提取id的辅助函数因京东商品 ID 既出现在 URL 路径也藏在script的window.location.href中需双重校验。3. 双平台适配细节淘宝司法频道的动态路由陷阱与京东司法频道的 Referer 强校验淘宝与京东司法频道虽同属电商系但技术栈差异极大。淘宝用 Vue Umi路由由前端控制URL 中不带真实 ID京东用 React Next.js服务端渲染但关键接口强制校验 Referer。若忽略这些细节脚本会在上线第三天集体失效。3.1 淘宝司法频道URL 无 ID靠window.location.hash动态拼接淘宝司法频道 URL 形如https://www.taobao.com/foreclosure所有标的列表通过 AJAX 加载点击进入详情页时 URL 变为https://www.taobao.com/foreclosure#detail?id123456但该id并非真实商品 ID而是前端生成的临时标识。真实 ID 存在于window.__INITIAL_STATE__的auction.item.id字段中且每次刷新会变。因此不能用 URL 中的id构造详情页链接而必须从列表页 HTML 中提取># 列表页中每个标的项的 HTML 片段 # div classitem>def get_jd_detail(session: requests.Session, item_id: str) - dict: # 列表页请求时已设置 headers包括 Referer 和 User-Agent detail_url fhttps://item.jd.com/api/detail?id{item_id}scene100 try: resp session.get(detail_url, timeout10) if resp.status_code 200: return resp.json() else: print(f京东详情接口异常 {resp.status_code}: {resp.text[:100]}) return {} except Exception as e: print(f京东详情请求失败 {item_id}: {e}) return {} # 调用前确保 session 已访问过列表页 session requests.Session() session.headers.update({ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Referer: https://pro.jd.com/foreclosure/?scene100 }) list_html session.get(https://pro.jd.com/foreclosure/).text血泪经验曾因忘记在Referer中添加?scene100导致连续 3 天采集失败日志里全是 403 却查不出原因。后来抓包对比浏览器请求才定位到这个隐藏参数。3.3 土地类字段专项处理从「宗地编号」到「土地终止日期」的标准化映射司法拍卖土地信息中同一字段在不同平台表述差异极大且常混入干扰字符。例如「宗地编号」淘宝宗地编号沪2023浦东新区不动产权第123456号京东【宗地编号】沪2023浦字123456实际需提取沪2023浦123456去掉括号、汉字、空格本方案在utils/field_normalizer.py中定义了字段清洗规则import re LAND_FIELD_RULES { parcel_number: [ # 匹配「宗地编号」后内容提取字母数字组合 (r宗地编号[:]\s*([A-Za-z\u4e00-\u9fa5]?[\d\u4e00-\u9fa5]), lambda m: re.sub(r[^\w], , m.group(1))), # 匹配「【宗地编号】」后内容 (r【宗地编号】[:]\s*([^\n]), lambda m: re.sub(r[^\w], , m.group(1))) ], land_use: [ (r土地用途[:]\s*(.?)\s*(?:||。|$), lambda m: m.group(1).strip()), (r用地性质[:]\s*(.?)\s*(?:||。|$), lambda m: m.group(1).strip()) ], end_date: [ (r终止日期[:]\s*(\d{4}年\d{1,2}月\d{1,2}日), lambda m: m.group(1).replace(年,).replace(月,).replace(日,)), (r使用期限至[:]\s*(\d{4}-\d{2}-\d{2}), lambda m: m.group(1).replace(-,)) ] } def normalize_field(text: str, field_name: str) - str: rules LAND_FIELD_RULES.get(field_name, []) for pattern, func in rules: if m : re.search(pattern, text): try: return func(m) except: continue return 玄学提示end_date的正则必须放在land_use之后执行——因为部分页面中「终止日期」文本紧挨着「土地用途」若先匹配end_date会把「土地用途住宅用地终止日期2030年12月31日」整个捕获导致land_use提取为空。4. 避坑指南五个让开发者凌晨三点还在看日志的真实翻车现场这套方案在某高校司法资产研究项目中已稳定运行 14 个月但前期踩过的坑足够写一本小册子。以下是高频、隐蔽、难排查的五类问题按「现象 → 原因 → 解决」结构整理每一条都来自真实翻车记录。4.1 现象淘宝页面render()后html.find()返回空列表但浏览器打开正常原因requests-html 默认使用 Chromium 旧版本v88无法正确执行淘宝页面中的Promise.allSettled()和Object.fromEntries()等新语法导致 JS 渲染失败DOM 未生成。解决升级 Chromium 内核。在requirements.txt中指定pyppeteer1.0.2并在代码中显式指定路径from pyppeteer import launch browser await launch(executablePath/usr/bin/chromium-browser) # Ubuntu 系统路径 # 或 Windows 下executablePathrC:\Program Files\Chromium\Application\chrome.exe4.2 现象京东采集任务运行 2 小时后突然全部 403重启即恢复原因京东服务端对单 IP 的Referer频率有限制连续 10 次请求相同Referer如https://pro.jd.com/foreclosure/?scene100会被标记为爬虫后续请求拒绝。解决动态轮换Referer中的 query 参数。在session.headers[Referer]中加入毫秒级时间戳import time timestamp int(time.time() * 1000) session.headers[Referer] fhttps://pro.jd.com/foreclosure/?scene100t{timestamp}4.3 现象「评估价」字段提取结果为0.0但网页显示为¥2,345.67万元原因正则r评估价[:]\s*(\d\.?\d*)未处理中文逗号分隔符和单位且未匹配「万元」换算逻辑。解决改用更鲁棒的数值提取函数支持单位自动换算def extract_currency(text: str) - float: # 先移除所有非数字、点、逗号、中文单位 clean re.sub(r[^\d.,\u4e00-\u9fa5], , text) # 匹配数字单位组合 if m : re.search(r(\d[.,\d]*)\s*(?:万元|万), clean): return float(m.group(1).replace(,, )) * 10000 elif m : re.search(r(\d[.,\d]*)\s*(?:元), clean): return float(m.group(1).replace(,, )) return 0.04.4 现象程序运行数日后># 在 selector_config.py 中 TAOBAO_ITEM_SELECTORS [ div[item-id], div[data-tb-id], div[data-jd-id] # 预留京东兼容 ] # 运行时逐个尝试首个返回非空结果者胜出4.5 现象导出 Excel 时「土地用途」列出现乱码如住宅用地但控制台打印正常原因pandasto_excel()默认使用utf-8编码写入但 Excel 2016 及以下版本默认用gbk解析导致中文显示为 UTF-8 字节流的乱码。解决强制用openpyxl引擎并设置单元格格式import pandas as pd from openpyxl import Workbook from openpyxl.styles import Font df.to_excel(output.xlsx, engineopenpyxl, indexFalse) # 后续用 openpyxl 打开并设置字体 wb Workbook() ws wb.active for cell in ws[1]: # 第一行标题 cell.font Font(nameMicrosoft YaHei, size10) wb.save(output.xlsx)5. 每日自动化落地从手动运行到 crontab 钉钉告警的完整闭环光有脚本能跑还不够真正的生产级落地需要「无人值守 异常感知 数据验证」三件套。我在某跨平台司法资产监测系统中把这套爬虫接入了标准运维流程每天 7:55 自动拉取昨日数据8:00 前生成 Excel 并推送钉钉群若失败则立即电话告警。以下是可直接复用的部署链路。5.1 用 crontab 实现每日定时触发精准控制启动时机Linux 服务器上crontab -e添加# 每天 7:55 执行采集避免早高峰网络拥堵 55 7 * * * cd /opt/judicial-crawler /usr/bin/python3 main.py --date$(date -d yesterday %Y-%m-%d) /var/log/judicial-crawler.log 21关键细节--date参数强制指定采集日期避免因服务器时间误差导致漏采追加日志而非覆盖方便回溯21将 stderr 合并到 stdout确保异常堆栈也被记录。5.2 输出 Excel 的字段校验与空值拦截机制Excel 不是终点而是数据质量卡点。我们在exporter.py中加入了三级校验必填字段检查parcel_number,land_use,appraisal_price三者缺一不可数值合理性检查appraisal_price必须 0 且 100 亿元超出视为异常去重检查基于platform parcel_number组合去重防止同标的一天内多次入库。def validate_and_export(df: pd.DataFrame, output_path: str): # 1. 必填字段过滤 required [parcel_number, land_use, appraisal_price] df_clean df.dropna(subsetrequired) # 2. 数值校验 df_valid df_clean[ (df_clean[appraisal_price] 0) (df_clean[appraisal_price] 1e10) ] # 3. 去重 df_dedup df_valid.drop_duplicates(subset[platform, parcel_number]) if len(df_dedup) 0: raise ValueError(校验后无有效数据请检查上游采集逻辑) # 导出并设置列宽 with pd.ExcelWriter(output_path, engineopenpyxl) as writer: df_dedup.to_excel(writer, indexFalse, sheet_name土地拍卖) worksheet writer.sheets[土地拍卖] for column in [A, B, C, D]: worksheet.column_dimensions[column].width 205.3 钉钉机器人推送失败告警 成功摘要消息体结构化钉钉机器人 Webhook 地址配置在config/settings.py中。推送逻辑区分成功/失败import requests import json def send_dingtalk_alert(title: str, content: str, is_success: bool True): webhook https://oapi.dingtalk.com/robot/send?access_tokenxxx payload { msgtype: markdown, markdown: { title: title, text: f## {title}\n\n {content}\n\n---\n- **执行时间**{datetime.now().strftime(%Y-%m-%d %H:%M:%S)}\n- **数据条数**{len(df) if df in locals() else 0} } } if not is_success: payload[at] {isAtAll: True} # 失败时所有人 requests.post(webhook, jsonpayload, timeout10) # 在 main.py 末尾调用 try: df crawl_all_platforms(yesterday) validate_and_export(df, foutput_{yesterday}.xlsx) send_dingtalk_alert(✅ 司法土地数据采集成功, f共采集 {len(df)} 条土地标的详见附件) except Exception as e: send_dingtalk_alert(❌ 司法土地数据采集失败, f错误{str(e)}请立即检查日志, is_successFalse)后悔药所有采集任务均开启--dry-run模式用于测试。加此参数后脚本只打印将要采集的 URL 列表不真正请求避免测试时触发反爬。从那以后我每次上线新平台适配都强制走一遍--dry-run→--validate-only→--real-run三步验证哪怕多花 10 分钟。因为一次漏掉京东scene100参数就让我在凌晨两点重跑了一整天的数据。希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑