一、项目前言
日常做农贸数据分析、生鲜行情调研时,菜市场实时报价是核心数据源。本文以乌鲁木齐某菜市场网页价格数据为爬取目标,采用 requests 做网络请求、 lxml 解析网页、 ThreadPoolExecutor 线程池实现并发提速,把全品类菜价结构化存入CSV文件,完整实现从网页抓取→数据清洗→本地存储全流程。
技术栈:Python3 + requests + lxml + 内置线程池 + CSV存储
二、页面分析
1. 爬取目标
该菜市场分页菜价列表,页面规律:URL通过页码参数翻页,每页为表格布局,表格每行 <tr> 对应一种菜品, <td> 单元格包含:品类、品名、规格、均价、产地、报价日期等字段。
2. 页面难点
表格单元格内部分文字嵌套 <em> 标签,直接 td.text 会丢失em标签内内容;解决方案:XPath语法 string(.) 提取单个 <td> 下所有嵌套标签合并后的完整文本,是本项目关键数据清洗技巧。
3. 并发选型
数据页数多达数百页,单线程循环爬取效率极低,使用 ThreadPoolExecutor 线程池,设置固定并发数批量提交任务,成倍缩短采集耗时。
三、爬虫源码
import requests from lxml import etree from concurrent.futures import ThreadPoolExecutor HEADERS = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" } def download(url): resp = requests.get(url, headers=HEADERS, timeout=10) resp.encoding = "utf-8" tree = etree.HTML(resp.text) trs = tree.xpath("//div[@class='bjbiao']/table/tr[position()>1]") lines = [] for tr in trs: # 关键修复:用 string(.) 取整个td的文本,合并 <em> 里的内容 tds = tr.xpath("./td") row = [] for td in tds: text = td.xpath("string(.)").strip() # 把 <td> 里所有文本合并成一个 row.append(text) lines.append(",".join(row)) with open("北园春蔬菜价格1.csv", "a", encoding="utf-8-sig") as f: f.write("\n".join(lines) + "\n") print("完成:", url) if __name__ == "__main__": # 表头初始化 with open("北园春蔬菜价格.csv", "w", encoding="utf-8-sig") as f: f.write("品种,最高价,最低价,中间价,分类,产地,市场,报价日期\n") base = "http://www.beiyuanchun.com/jiagexingqing/?&chandi=%E4%B9%8C%E9%B2%81%E6%9C%A8%E9%BD%90&pz=&rqstart=&rqend=&fl=%E8%94%AC%E8%8F%9C&sc=&page={}" with ThreadPoolExecutor(max_workers=15) as exe: for page in range(1, 3974): url = base.format(page) exe.submit(download, url)运行结果:
完成: http://www.beiyuanchun.com/jiagexingqing/?&chandi=%E4%B9%8C%E9%B2%81%E6%9C%A8%E9%BD%90&pz=&rqstart=&rqend=&fl=%E8%94%AC%E8%8F%9C&sc=&page=4 完成: http://www.beiyuanchun.com/jiagexingqing/?&chandi=%E4%B9%8C%E9%B2%81%E6%9C%A8%E9%BD%90&pz=&rqstart=&rqend=&fl=%E8%94%AC%E8%8F%9C&sc=&page=5 完成: http://www.beiyuanchun.com/jiagexingqing/?&chandi=%E4%B9%8C%E9%B2%81%E6%9C%A8%E9%BD%90&pz=&rqstart=&rqend=&fl=%E8%94%AC%E8%8F%9C&sc=&page=1 完成: http://www.beiyuanchun.com/jiagexingqing/?&chandi=%E4%B9%8C%E9%B2%81%E6%9C%A8%E9%BD%90&pz=&rqstart=&rqend=&fl=%E8%94%AC%E8%8F%9C&sc=&page=11 完成: http://www.beiyuanchun.com/jiagexingqing/?&chandi=%E4%B9%8C%E9%B2%81%E6%9C%A8%E9%BD%90&pz=&rqstart=&rqend=&fl=%E8%94%AC%E8%8F%9C&sc=&page=6 完成: http://www.beiyuanchun.com/jiagexingqing/?&chandi=%E4%B9%8C%E9%B2%81%E6%9C%A8%E9%BD%90&pz=&rqstart=&rqend=&fl=%E8%94%AC%E8%8F%9C&sc=&page=12 完成: http://www.beiyuanchun.com/jiagexingqing/?&chandi=%E4%B9%8C%E9%B2%81%E6%9C%A8%E9%BD%90&pz=&rqstart=&rqend=&fl=%E8%94%AC%E8%8F%9C&sc=&page=2 完成: http://www.beiyuanchun.com/jiagexingqing/?&chandi=%E4%B9%8C%E9%B2%81%E6%9C%A8%E9%BD%90&pz=&rqstart=&rqend=&fl=%E8%94%AC%E8%8F%9C&sc=&page=3 完成: http://www.beiyuanchun.com/jiagexingqing/?&chandi=%E4%B9%8C%E9%B2%81%E6%9C%A8%E9%BD%90&pz=&rqstart=&rqend=&fl=%E8%94%AC%E8%8F%9C&sc=&page=9 完成: http://www.beiyuanchun.com/jiagexingqing/?&chandi=%E4%B9%8C%E9%B2%81%E6%9C%A8%E9%BD%90&pz=&rqstart=&rqend=&fl=%E8%94%AC%E8%8F%9C&sc=&page=7 完成: http://www.beiyuanchun.com/jiagexingqing/?&chandi=%E4%B9%8C%E9%B2%81%E6%9C%A8%E9%BD%90&pz=&rqstart=&rqend=&fl=%E8%94%AC%E8%8F%9C&sc=&page=13 完成: http://www.beiyuanchun.com/jiagexingqing/?&chandi=%E4%B9%8C%E9%B2%81%E6%9C%A8%E9%BD%90&pz=&rqstart=&rqend=&fl=%E8%94%AC%E8%8F%9C&sc=&page=10 完成: http://www.beiyuanchun.com/jiagexingqing/?&chandi=%E4%B9%8C%E9%B2%81%E6%9C%A8%E9%BD%90&pz=&rqstart=&rqend=&fl=%E8%94%AC%E8%8F%9C&sc=&page=15 完成: http://www.beiyuanchun.com/jiagexingqing/?&chandi=%E4%B9%8C%E9%B2%81%E6%9C%A8%E9%BD%90&pz=&rqstart=&rqend=&fl=%E8%94%AC%E8%8F%9C&sc=&page=16 完成: http://www.beiyuanchun.com/jiagexingqing/?&chandi=%E4%B9%8C%E9%B2%81%E6%9C%A8%E9%BD%90&pz=&rqstart=&rqend=&fl=%E8%94%AC%E8%8F%9C&sc=&page=8 完成: http://www.beiyuanchun.com/jiagexingqing/?&chandi=%E4%B9%8C%E9%B2%81%E6%9C%A8%E9%BD%90&pz=&rqstart=&rqend=&fl=%E8%94%AC%E8%8F%9C&sc=&page=14 完成: http://www.beiyuanchun.com/jiagexingqing/?&chandi=%E4%B9%8C%E9%B2%81%E6%9C%A8%E9%BD%90&pz=&rqstart=&rqend=&fl=%E8%94%AC%E8%8F%9C&sc=&page=17 完成: http://www.beiyuanchun.com/jiagexingqing/?&chandi=%E4%B9%8C%E9%B2%81%E6%9C%A8%E9%BD%90&pz=&rqstart=&rqend=&fl=%E8%94%AC%E8%8F%9C&sc=&page=19 完成: http://www.beiyuanchun.com/jiagexingqing/?&chandi=%E4%B9%8C%E9%B2%81%E6%9C%A8%E9%BD%90&pz=&rqstart=&rqend=&fl=%E8%94%AC%E8%8F%9C&sc=&page=18 完成: http://www.beiyuanchun.com/jiagexingqing/?&chandi=%E4%B9%8C%E9%B2%81%E6%9C%A8%E9%BD%90&pz=&rqstart=&rqend=&fl=%E8%94%AC%E8%8F%9C&sc=&page=20 完成: http://www.beiyuanchun.com/jiagexingqing/?&chandi=%E4%B9%8C%E9%B2%81%E6%9C%A8%E9%BD%90&pz=&rqstart=&rqend=&fl=%E8%94%AC%E8%8F%9C&sc=&page=21 完成: http://www.beiyuanchun.com/jiagexingqing/?&chandi=%E4%B9%8C%E9%B2%81%E6%9C%A8%E9%BD%90&pz=&rqstart=&rqend=&fl=%E8%94%AC%E8%8F%9C&sc=&page=24 完成: http://www.beiyuanchun.com/jiagexingqing/?&chandi=%E4%B9%8C%E9%B2%81%E6%9C%A8%E9%BD%90&pz=&rqstart=&rqend=&fl=%E8%94%AC%E8%8F%9C&sc=&page=27 完成: http://www.beiyuanchun.com/jiagexingqing/?&chandi=%E4%B9%8C%E9%B2%81%E6%9C%A8%E9%BD%90&pz=&rqstart=&rqend=&fl=%E8%94%AC%E8%8F%9C&sc=&page=29 完成: http://www.beiyuanchun.com/jiagexingqing/?&chandi=%E4%B9%8C%E9%B2%81%E6%9C%A8%E9%BD%90&pz=&rqstart=&rqend=&fl=%E8%94%AC%E8%8F%9C&sc=&page=23 ... 完成: http://www.beiyuanchun.com/jiagexingqing/?&chandi=%E4%B9%8C%E9%B2%81%E6%9C%A8%E9%BD%90&pz=&rqstart=&rqend=&fl=%E8%94%AC%E8%8F%9C&sc=&page=3973 完成: http://www.beiyuanchun.com/jiagexingqing/?&chandi=%E4%B9%8C%E9%B2%81%E6%9C%A8%E9%BD%90&pz=&rqstart=&rqend=&fl=%E8%94%AC%E8%8F%9C&sc=&page=3972 完成: http://www.beiyuanchun.com/jiagexingqing/?&chandi=%E4%B9%8C%E9%B2%81%E6%9C%A8%E9%BD%90&pz=&rqstart=&rqend=&fl=%E8%94%AC%E8%8F%9C&sc=&page=3969 完成: http://www.beiyuanchun.com/jiagexingqing/?&chandi=%E4%B9%8C%E9%B2%81%E6%9C%A8%E9%BD%90&pz=&rqstart=&rqend=&fl=%E8%94%AC%E8%8F%9C&sc=&page=3971四、代码逐模块讲解
1. 依赖与请求头配置
requests :负责HTTP网络请求,模拟浏览器访问页面;
lxml.etree :高性能HTML/XML解析器,配合XPath精准定位页面元素;
ThreadPoolExecutor :Python内置线程池,不用手动创建销毁线程,自动管理并发队列;
HEADERS :UA伪装,解决服务器拦截Python默认爬虫标识的问题。
2. download()核心爬取函数
1. 请求页面:设置超时10秒,防止页面卡死阻塞爬虫;指定 utf-8 编码,解决中文乱码;
2. XPath定位表格: //div[@class="bjbiao"]/table/tr[position()>1] , position()>1 跳过表格表头行,只取数据行;
3. 关键数据提取:string(.)
常规 td.text 只能获取标签直接文本,若 <td>大白菜<em>精品</em></td> , .text 只会拿到 大白菜 ;
td.xpath("string(.)") 会递归提取td下所有子节点文本,结果为 大白菜精品 ,完美适配页面嵌套标签场景;
4. 异常捕获:单页爬取报错不中断整体程序,打印失败链接方便后续补爬。
3. 主线程:表头初始化和线程池调度
1. CSV预处理:使用 utf-8-sig 编码,Excel打开CSV不会出现中文乱码;程序启动先写入字段表头;
2. 线程池批量提交任务:循环拼接每一页URL, exe.submit(download, url) 异步分发爬取任务, max_workers=15 控制并发数,防止并发过高被网站封IP;
3. 收尾落地数据:所有页面爬取完成后,一次性把全部数据写入CSV,减少频繁IO操作、提升文件写入效率。
五、爬虫运行效果
1. 控制台输出:运行后控制台逐行打印页面URL;
2. 文件产出:项目目录生成 北园春菜市场菜价.csv ,可用Excel/WPS直接打开,字段规整:品类、菜名、单价、产地、日期一一对应;
3. 效率对比:397页数据,单线程需要数分钟,15并发线程池几十秒即可完成全量采集。
六、拓展方案
1. 反爬优化
增加随机请求间隔: import time + random.sleep(random.uniform(0.1,0.5)) ,在请求前随机休眠,降低访问频率;
代理IP:高频爬取触发封禁时,接入代理池轮换IP;
补充Cookie:部分页面需要Cookie鉴权,在HEADERS中追加Cookie字段。
2. 数据存储拓展
存入数据库:把 all_data 数据解析后,使用 pymysql 存入MySQL,方便后续SQL筛选、价格统计;
Pandas处理:爬完后用 pandas.read_csv() 读取CSV,做均价排序、月度价格走势可视化。
fail_urls = [] # 全局存失败链接 # download异常分支:fail_urls.append(url) # 爬完后二次循环重试失败链接七、合规与提醒
1. 爬虫合规:本项目仅用于个人学习数据分析,禁止商用批量爬取网站数据;爬取前遵守目标网站 robots.txt 协议,控制并发和爬速,避免对目标服务器造成压力;
2. 网站变动兼容:若后续网站改版(class名称、页面结构变更),只需修改XPath表达式和基础URL即可适配。
八、总结
本案例是表格类网页爬虫标准实战模板,吃透3个核心知识点:
1. XPath string(.) 提取含嵌套标签的单元格文本;
2. ThreadPoolExecutor线程池并发爬虫的工程写法;
3. CSV utf-8-sig 编码解决Excel中文乱码。