ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

农产品爬虫实战:requests+re+csv轻量方案解析

2026/9/16 3:08:04 拓冰建站 浏览量
农产品爬虫实战:requests+re+csv轻量方案解析 简介本资源是一个面向Python初学者与数据采集实践者的农产品领域网络爬虫项目聚焦于使用主流库完成网页数据抓取、清洗与结构化存储。项目以“ncp爬虫4”为名属系列进阶实践涵盖requests发起请求、BeautifulSoup/PyQuery解析HTML、正则与字符串处理清洗农产品信息、CSV格式持久化等完整链路并隐含应对反爬如User-Agent轮换、请求延时的实用技巧。压缩包仅含1个核心Python脚本ncp爬虫4.py体积精简至2KB便于快速阅读、调试与二次开发适合嵌入教学案例或小型数据采集任务。目前已有1351人学习下载读者可直接复用源码逻辑掌握农产品价格、产地、品类等结构化数据的端到端采集方法并获得清晰的模块化代码结构与基础数据分析思路。1. 农产品数据采集不是“写个requests就完事”——ncp爬虫4的实战边界在哪你用requests.get(url)抓了10页蔬菜价格发现第11页返回403你把BeautifulSoup(html, lxml)套进循环跑着跑着内存飙到4GB你导出CSV后打开Excel发现“黄瓜_山东寿光_2024-05-20”字段里混着广告脚本和乱码空格。这不是代码写错了而是农产品爬虫的真实水位线——它不只考Python语法更考对农业信息网站结构的理解、对动态加载与反爬策略的预判、对非结构化文本中商品实体的识别能力。ncp爬虫4.py正是这样一个踩过坑、调过参、改过三次解析逻辑的生产级片段它不追求全站通杀而是聚焦于NCP国家农产品质量安全追溯平台类站点的典型响应模式用最小依赖完成可复现的数据提取闭环。适合已有Python基础、正从“能跑通”迈向“能上线”的开发者尤其适用于需要对接本地农技推广站、批发市场信息系统或做区域农产品价格监测的场景。2. 为什么选requestsrecsv而非Scrapy或Selenium——ncp爬虫4的技术选型逻辑2.1 农产品数据源的三个硬约束决定技术栈取舍农产品公开数据平台如地方农业农村厅子站、NCP试点平台、部分产地批发市场公示页普遍存在三类特征第一页面静态HTML占比高核心数据多嵌在table或div classprice-list中极少依赖Vue/React前端渲染第二反爬强度中等——无验证码但有Referer校验、User-Agent白名单、请求频率阈值通常3秒/次即触发限流第三数据结构高度重复固定字段如“品名、产地、规格、单价、日期、单位”但HTML标签嵌套深度不一常含冗余span包裹、空格换行、中文括号干扰。这直接排除了重型框架Scrapy的中间件链和调度器在此场景下是冗余开销Selenium启动浏览器实例既慢又易被检测且对纯表格数据无必要。而requests轻量可控re精准匹配非标准HTMLcsv直写避免Pandas依赖——三者组合恰是资源受限环境下的最优解。提示本项目未使用lxml或BeautifulSoup因实测发现某省农产品价格网返回的HTML存在大量未闭合标签如trtd.../tr导致BS4解析时自动补全逻辑误吞相邻行数据。正则表达式虽需手动维护但在字段位置稳定、结构碎片化的场景下反而更鲁棒。2.2 requests会话管理与反爬规避的关键参数配置ncp爬虫4.py中requests.Session()的初始化并非简单调用而是针对农产品站点特性做了四层加固import requests import time import random session requests.Session() # 1. 模拟真实浏览器行为设置完整Headers session.headers.update({ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36, Accept: text/html,application/xhtmlxml,application/xml;q0.9,*/*;q0.8, Accept-Language: zh-CN,zh;q0.9,en-US;q0.8,en;q0.7, Accept-Encoding: gzip, deflate, Connection: keep-alive, Upgrade-Insecure-Requests: 1, # 关键伪造Referer绕过来源校验 Referer: https://www.example-agri.gov.cn/price/ }) # 2. 设置连接超时与重试策略 adapter requests.adapters.HTTPAdapter( pool_connections10, pool_maxsize10, max_retries3 # 连接失败时重试3次 ) session.mount(http://, adapter) session.mount(https://, adapter) # 3. 请求间隔控制避免触发IP限流 def safe_get(url, **kwargs): time.sleep(random.uniform(3.2, 5.8)) # 非固定间隔防模式识别 try: response session.get(url, timeout(10, 30), **kwargs) response.raise_for_status() # 抛出HTTP错误 return response except requests.exceptions.RequestException as e: print(f请求失败 {url}: {e}) return NoneUser-Agent字符串明确指向Chrome最新稳定版且包含Win64标识——多数农业网站的UA白名单仅校验是否为常见桌面浏览器不深究版本号Referer必须与目标页面实际来源一致否则返回403实测某市农业农村局网站强制校验Referer域名time.sleep区间设为3.2~5.8秒而非整数秒因部分站点采用滑动窗口计数固定间隔易被识别为脚本timeout(10,30)中10秒为连接超时30秒为读取超时兼顾网络抖动与大HTML响应。2.3 正则解析农产品字段的精确锚点设计面对td西红柿/tdtd山东寿光/tdtd一级/tdtd¥5.20/kg/tdtd2024-05-20/td这类结构ncp爬虫4.py放弃通用find_all(td)转而用正则锚定语义边界import re # 定义字段提取正则已适配ncp系列站点HTML特征 PRICE_PATTERN rtd[^]*?(?:nbsp;|#160;|\s)*([一-龥\w\u4e00-\u9fa5])(?:nbsp;|#160;|\s)*/td\s*td[^]*?(?:nbsp;|#160;|\s)*([一-龥\w\u4e00-\u9fa5])(?:nbsp;|#160;|\s)*/td\s*td[^]*?(?:nbsp;|#160;|\s)*([\d\.])(?:nbsp;|#160;|\s)*(?:元|¥|)(?:/|每)?(?:kg|斤|公斤|千克|吨)?(?:nbsp;|#160;|\s)*/td\s*td[^]*?(?:nbsp;|#160;|\s)*(\d{4}-\d{2}-\d{2})(?:nbsp;|#160;|\s)*/td def extract_agri_data(html_content): results [] for match in re.finditer(PRICE_PATTERN, html_content, re.DOTALL | re.IGNORECASE): product, origin, price, date match.groups() # 清洗去除不可见字符、全角空格、多余标点 product re.sub(r[\u3000\s], , product).strip() origin re.sub(r[\u3000\s], , origin).strip() price re.sub(r[^\d\.], , price) # 仅保留数字和小数点 if not (product and origin and price and date): continue results.append({ product: product, origin: origin, price: float(price), date: date, unit: 元/kg # 根据上下文动态推断此处简化为固定值 }) return resultsre.DOTALL使.匹配换行符解决HTML跨行问题re.IGNORECASE忽略大小写适配TD或td混用字段捕获组(.*?)外层包裹(?:nbsp;|#160;|\s)*覆盖常见空格编码price清洗逻辑re.sub(r[^\d\.], , price)比str.replace()更可靠能同时剔除¥、/kg、全角斜杠等干扰product和origin清洗使用[\u3000\s]覆盖中文全角空格\u3000和ASCII空格。2.4 CSV写入的字段对齐与编码容错处理农产品数据常含生僻地名如“勐腊县”、“阿克苏地区”和特殊符号“¥”、“—”直接csv.writer易报UnicodeEncodeError。ncp爬虫4.py采用BOM头UTF-8-SIG编码并预设字段顺序import csv def save_to_csv(data_list, filenameagri_prices.csv): if not data_list: print(无数据可保存) return # 确保字段顺序统一避免Excel列错位 fieldnames [product, origin, price, date, unit] with open(filename, w, newline, encodingutf-8-sig) as f: writer csv.DictWriter(f, fieldnamesfieldnames) writer.writeheader() for row in data_list: # 补全缺失字段防止DictWriter报错 filled_row {k: row.get(k, ) for k in fieldnames} writer.writerow(filled_row) print(f成功写入 {len(data_list)} 条记录到 {filename}) # 调用示例 data extract_agri_data(html_content) save_to_csv(data, shouguang_tomato_20240520.csv)encodingutf-8-sig在文件开头写入BOMByte Order Mark确保Windows记事本和Excel正确识别UTF-8编码fieldnames显式声明并用于DictWriter强制列顺序避免因字典键序随机导致CSV列颠倒row.get(k, )为每个字段提供默认空值防止某条记录缺失unit字段时写入失败。3. 从ncp爬虫4.py源码看农产品爬虫的四层调试路径3.1 源码结构拆解模块化设计如何支撑快速迭代ncp爬虫4.py虽仅237行但严格分层模块行号范围核心职责可替换性说明配置区1-22BASE_URL,TARGET_PAGES,HEADERS,DELAY_RANGE等全局参数修改URL和延时区间即可适配新站点工具函数区24-68safe_get(),extract_agri_data(),clean_text(),save_to_csv()extract_agri_data()需按新HTML重写主逻辑区70-185循环请求、解析、去重、合并结果、异常日志增加if page_type wholesale分支可扩展执行入口区187-237if __name__ __main__:启动参数解析、进度条、最终保存支持命令行传参--date 2024-05-20这种结构使二次开发成本极低若需新增“批发市场”数据源只需在配置区添加WHOLESALE_URLS在主逻辑区复制一个fetch_wholesale_data()函数调用即可无需改动解析引擎。3.2 调试第一步验证HTTP响应有效性而非直接解析新手常犯错误是拿到response.text立刻丢给解析函数却忽略状态码和内容真实性。ncp爬虫4.py在safe_get()后强制校验response session.get(url, timeout(10, 30)) if response.status_code ! 200: print(fHTTP {response.status_code} 错误: {url}) return None # 关键检查响应内容是否为HTML防重定向到登录页 if text/html not in response.headers.get(Content-Type, ): print(f非HTML响应: {url} - {response.headers.get(Content-Type)}) return None # 关键检查是否含预期关键词防反爬跳转页 if 请输入验证码 in response.text or 访问过于频繁 in response.text: print(f疑似触发反爬: {url}) return NoneContent-Type校验过滤掉JSON接口或图片重定向关键词扫描请输入验证码比检查input typecaptcha更高效因反爬页HTML结构多变此步放在解析前避免无效HTML进入正则引擎导致re.finditer空耗CPU。3.3 调试第二步用正则调试器验证HTML片段匹配精度当extract_agri_data()返回空列表切忌直接改代码。应先提取原始HTML片段# 在safe_get()后添加调试代码 with open(debug_raw.html, w, encodingutf-8) as f: f.write(response.text[:10000]) # 保存前1万字符然后将debug_raw.html粘贴至在线正则调试器如regex101.com输入PRICE_PATTERN启用g全局和sDOTALL标志。重点观察捕获组是否准确框选西红柿、山东寿光等文本是否因br标签或注释!-- price --导致匹配中断若匹配失败用re.findall(rtd[^]*?.*?/td, html, re.DOTALL)先提取所有td人工确认目标字段位置。注意不要在生产代码中打印完整response.text因其可能含敏感信息如内部IP、调试开关。调试时用[:10000]截断是安全实践。3.4 调试第三步字段清洗效果可视化验证清洗逻辑clean_text()常引入新bug。ncp爬虫4.py提供dry_run模式def dry_run_cleaning(html_sample): 演示清洗效果不写入文件 raw_matches re.findall(rtd[^]*?(.*?)/td, html_sample, re.DOTALL) print(原始td内容:, raw_matches[:3]) cleaned [re.sub(r[\u3000\s], , s).strip() for s in raw_matches] print(清洗后:, cleaned[:3]) # 调用示例取自debug_raw.html sample_html td 西红柿 /tdtd山东nbsp;nbsp;寿光/td dry_run_cleaning(sample_html) # 输出 # 原始td内容: [ 西红柿 , 山东nbsp;nbsp;寿光] # 清洗后: [西红柿, 山东寿光]该函数直观展示正则清洗前后对比避免凭空猜测re.sub()行为。3.5 调试第四步CSV输出验证与Excel兼容性测试保存后务必用Excel而非文本编辑器打开CSV检查中文是否乱码乱码则证明未用utf-8-sig价格列是否被Excel误识别为日期如5.20变成5月20日此时需在CSV中为数值字段加英文引号5.20地名是否因逗号导致列错位如云南,昆明需整体加引号。ncp爬虫4.py未自动加引号因多数农产品字段不含逗号。若需支持修改save_to_csv()中writer.writerow()为# 对含逗号或引号的字段自动加引号 for k, v in filled_row.items(): if isinstance(v, str) and (, in v or in v): filled_row[k] f{v.replace(, )} # Excel兼容双引号转义4. 农产品爬虫的增量更新与去重实战基于日期与哈希的双保险策略4.1 为什么农产品数据必须增量更新农产品价格具有强时效性早市价与晚市价可差15%周环比波动超20%。全量重爬不仅浪费带宽更导致历史数据被覆盖。ncp爬虫4.py通过--since-date参数实现增量python ncp爬虫4.py --since-date 2024-05-15其核心逻辑在主函数中import sys from datetime import datetime, timedelta def parse_date_arg(): 解析命令行日期参数 if --since-date in sys.argv: idx sys.argv.index(--since-date) if idx 1 len(sys.argv): try: return datetime.strptime(sys.argv[idx 1], %Y-%m-%d).date() except ValueError: print(日期格式错误请使用 YYYY-MM-DD) sys.exit(1) return datetime.today().date() - timedelta(days7) # 默认取近7天 # 主逻辑中构建URL列表 start_date parse_date_arg() date_range [start_date timedelta(daysi) for i in range(7)] urls [fhttps://price.example.gov.cn/{d.strftime(%Y%m%d)}.html for d in date_range]timedelta(days7)设为默认回溯周期平衡数据新鲜度与服务器压力datetime.strptime()严格校验格式避免2024-5-15等非法输入导致strptime崩溃。4.2 哈希去重解决同一商品多规格重复录入同一日“西红柿”可能有“一级”、“二级”、“大棚”、“露天”四种规格价格不同但品名相同。若仅按productdate去重会丢失规格维度。ncp爬虫4.py采用字段组合哈希import hashlib def generate_record_hash(record): 基于关键字段生成唯一哈希避免同品名不同规格被去重 # 拼接字符串品名产地规格若存在价格日期 key_str f{record[product]}|{record[origin]}|{record.get(spec, )}|{record[price]}|{record[date]} return hashlib.md5(key_str.encode(utf-8)).hexdigest()[:16] # 去重逻辑 seen_hashes set() deduped_data [] for record in all_data: h generate_record_hash(record) if h not in seen_hashes: seen_hashes.add(h) deduped_data.append(record)key_str包含spec字段规格若原始HTML中无此字段则record.get(spec, )返回空字符串不影响哈希一致性hexdigest()[:16]截取前16位足够区分百万级记录且节省内存此哈希法比pandas.DataFrame.drop_duplicates()更轻量避免引入Pandas依赖。4.3 增量结果合并避免覆盖历史CSV的原子操作直接open(agri_prices.csv, w)会清空旧文件。ncp爬虫4.py采用临时文件原子重命名import os import tempfile def append_to_csv(new_data, filenameagri_prices.csv): # 1. 读取现有数据若存在 existing_data [] if os.path.exists(filename): with open(filename, r, encodingutf-8-sig) as f: reader csv.DictReader(f) existing_data list(reader) # 2. 合并去重按哈希 all_data existing_data new_data seen set() merged [] for row in all_data: h generate_record_hash(row) if h not in seen: seen.add(h) merged.append(row) # 3. 写入临时文件再原子替换 with tempfile.NamedTemporaryFile(modew, deleteFalse, encodingutf-8-sig, newline) as tmp: writer csv.DictWriter(tmp, fieldnames[product,origin,price,date,unit]) writer.writeheader() writer.writerows(merged) tmp_path tmp.name os.replace(tmp_path, filename) # 原子操作避免写入中断导致文件损坏 print(f合并后共 {len(merged)} 条记录已更新 {filename}) # 调用 append_to_csv(new_data, agri_prices.csv)tempfile.NamedTemporaryFile确保临时文件与目标文件同磁盘分区os.replace()在Linux/macOS上为原子操作deleteFalse防止临时文件被自动删除便于调试此方案保证即使程序在写入中途崩溃原CSV文件仍完好。4.4 验证增量更新效果的三步检查法执行增量爬取后用以下命令快速验证# 1. 查看最新10条记录确认新数据已加入 tail -n 10 agri_prices.csv | head -n 5 # 2. 统计各日期记录数确认未重复抓取旧数据 awk -F, NR1 {print $4} agri_prices.csv | sort | uniq -c | sort -nr # 3. 检查哈希去重是否生效同品名同日期应仅一条 awk -F, NR1 $1西红柿 $42024-05-20 {print} agri_prices.csv | wc -ltail/head组合避免加载整个大文件awk -F, NR1跳过CSV表头uniq -c统计重复项sort -nr按数量降序排列一眼识别异常高频日期最后一行命令验证特定商品在指定日期的去重结果wc -l输出应为1若有多条说明哈希逻辑需调整。本文还有配套的精品资源点击获取