☰
基于Django的农产品电商爬虫模块设计:数据采集与大数据链路实战
2026/10/5 3:53:00 网站建设 项目流程

做农产品电商平台的时候,数据来源一直是个绕不开的坎。平台方自己录入商品信息不仅效率低,而且价格、供应量、产地行情这类数据更新不及时,整个平台的参考价值会大打折扣。我当时的方案是围绕 Django 单独做了一个爬虫模块,负责从公开渠道采集农产品价格、产地、品种、供应量这些基础数据,再配合大数据技术做清洗、分析和展示,这也就是"基于 Django 的大数据技术的农产品电商平台设计与实现"里爬虫部分的由来。

这篇文章我会完整拆解这个爬虫模块的设计思路、核心实现、反爬处理、大数据链路上的数据流转,以及我在实际开发中踩过的一些坑。适合正在做 Django 项目实战的学生、想给电商平台补数据采集能力的开发者,以及对 Python 爬虫如何落地到 Web 项目里感兴趣的人。我不会只贴代码,而是把为什么这么设计、每一步解决什么问题讲清楚,这样你拿去改成别的垂直领域电商也能直接套思路。

1. 项目定位与爬虫模块的整体设计思路

1.1 电商平台里为什么单独拆一个"爬虫模块"

农产品电商和普通标品电商差别很大。标品比如数码产品,SKU 稳定、参数明确、价格相对固定,人工维护商品库是可行的。但农产品不一样,同一种蔬菜在不同产区、不同季节、不同批发市场的价格波动非常大,再加上各地多个供货渠道,如果完全靠运营人员手动维护,数据滞后不说,人力成本也完全扛不住。

所以在设计这个平台时,我一开始就把爬虫定位成"数据采集基础设施",而不是一个挂在某个页面后面的小工具。它要解决三件事:

  • 商品信息的自动录入和定期更新,减少人工维护成本。
  • 第三方公开行情数据的补充,比如批发市场每日报价。
  • 为后续大数据分析提供原始数据源,比如价格趋势、产地分布、供应量变化。

这个定位决定了它的架构不能是一个独立脚本,而是要嵌在 Django 项目里,能够被定时任务调度、能被后台管理页面监控、采集结果能直接落到业务数据库,同时还要把原始数据同步到大数据库做分析。

1.2 选型:Django 作为调度外壳、Python 爬虫作为数据采集

为什么爬虫模块要基于 Django 而不是单独写一套框架,这个选择我想多说几句。

首先是复用现有技术栈。项目本身就是 Django 写的,那爬虫调度、任务记录、数据展示直接用 Django 的 ORM、Admin 后台和缓存机制,不需要额外引入一套调度系统。Django 的 ORM 在爬虫场景下虽然不算性能最强的,但够用而且开发效率高,尤其在数据量日均几万条这个量级,完全能扛住。

其次是便于异常处理和可视化。爬虫运行会有一堆状态需要跟踪,比如任务是否成功、抓了多少条、失败多少条、耗时多久。这些记录如果只存在脚本日志里,排查问题非常痛苦。放到 Django 里,我可以直接建几张表来记录任务状态,后台一查便知。

采集端我用的是 requests + lxml + XPath 这套组合。Scrapy 确实功能更强,有内置的下载中间件、爬虫规则和 Item Pipeline,但在这个项目里很多采集源是定制化的 API 接口,用 Scrapy 反而显得重。requests 配合 XPath 足够灵活,遇到动态加载的页面再补 Selenium 或者直接模拟请求。这里没有绝对的对错,核心逻辑是"够用就好、扩展留口"。

1.3 大数据视角下的爬虫边界:采集、清洗、入仓

爬虫在整个大数据链路里扮演的是最上游的数据源角色。很多做大数据的人容易忽略一个问题:模型分析、可视化大屏的前提是得有干净、完整、口径统一的数据,而数据质量恰恰是靠爬虫这一层来保障的。

我当时把爬虫模块划分为三个子阶段:

  1. 采集:从目标站点拿到原始 HTML 或 JSON 数据。
  2. 清洗:将原始数据转换成结构化记录,包括字段抽取、单位换算、去重、缺失值处理。
  3. 入仓:清洗后的数据写入业务库和分析库。

这三个阶段并不是一次性做完就完事,而是每次采集任务都要走一遍。业务库存的是平台展示用的最终数据,分析库存的是用于趋势分析的明细历史数据,两者数据粒度不同,用途也不同。这个设计在后来的价格趋势分析中派上了大用场,因为历史明细数据是做时间序列分析的基础,如果只保留最后结果,分析就无从下手。

2. 爬虫模块的数据库设计与调度机制

2.1 商品、产地、价格、爬取任务这几张表怎么设计

爬虫模块的数据库设计我一共建了 5 张核心表,每张表都有明确的职责边界:商品表、产地表、价格行情表、爬虫任务表、原始数据表。

商品表存的是农产品的基础信息,包括品类、品种、规格、单位。这里要注意一个细节:"品类"和"品种"是两级概念,比如品类是"叶菜类",品种是"菠菜"。如果只做一张单层表,后续扩展不同地区的叫法差异时会非常痛苦。

产地表相对独立,因为同一个产地会对应多个商品。产地字段包括省份、城市、区县、产地名称,我加了一个唯一约束在省市县三级上,防止重复写入。

价格行情表是核心业务表,记录某个商品在某天某产地供货商给出的价格,包括最低价、最高价、均价、单位、更新时间。这张表的数据量会快速膨胀,所以一定要按日期建索引,查询效率差千万倍。

爬虫任务表用来记录每一次采集任务的执行情况。字段包括任务名称、目标站点、状态、开始时间、结束时间、成功数、失败数、错误信息。这个表是排查问题的第一入口,如果任务失败,错误信息会直接记录在这里。

原始数据表是另一种思路,它把抓到的未解析数据先原样存一份。这个表一开始我觉得多余,但后来证明它非常有用。因为解析逻辑如果写错了,至少原始数据还在,可以重跑解析而不需要重新抓取。对于反爬严格的目标站点来说,这简直是救命设计。

2.2 Django 定时任务与爬虫调度的衔接

定时任务我用的是 Django-celery-beat,Celery 负责异步执行爬虫任务,Beat 负责定时触发。为什么不直接用 crontab,原因是爬虫任务的执行状态需要写回数据库,而且多个爬虫任务之间可能有依赖关系,比如先抓取列表页再抓取详情页,这些用 crontab 管理起来很别扭。

调度流程是这样的:

  1. Beat 根据配置的 crontab 定时发送任务到 Celery Broker(我用的是 Redis)。
  2. Celery Worker 接收到任务,调用爬虫执行函数。
  3. 爬虫函数执行前先创建一条任务记录,状态为"执行中"。
  4. 执行结束后更新任务记录,写入成功/失败数量和错误信息。

这里有个很重要的经验:爬虫任务函数必须做超时控制。我用的是timeout参数配合信号机制,单个请求超过 15 秒就放弃。因为农产品行情页面看起来简单,但偶尔会出现服务器响应极慢的情况,如果没有超时控制,Celery Worker 会被一个慢请求卡死,后面的任务全部排队。

2.3 分布式采集思路:多节点采集同一任务

单机爬虫的瓶颈迟早会出现,尤其是当采集的目标站点增多、采集频率提高之后。我在这个项目里虽然没有把分布式做到极致,但设计了可以扩展的分布式采集结构。

具体做法是把爬虫任务按"目标站点+采集类型"拆分成独立任务单元,发布到同一个 Redis Broker。多个服务器节点上各跑一个 Celery Worker,同时监听队列。因为每个节点拿到的任务不同,天然就实现了负载分散。

分布式采集需要注意的问题有两个。一个是任务幂等性,同一个商品同一天的价格如果被两个节点同时抓取,后写入的应该覆盖先写入的,或者直接判断已存在就跳过,否则会出现重复数据。另一个是不要所有节点共享同一个出口 IP,否则目标站点反爬会非常明显,后面会专门讲代理池的问题。

3. 核心爬虫实现与反爬处理

3.1 requests + XPath 的常规采集流程

先讲一个最常规的采集流程,以某个农产品批发市场的公开价格页面为例。正常思路是先请求列表页,拿到每个商品详情页的链接,再逐个请求详情页提取数据。但农产品行情站的页面结构通常比较简单,很多时候列表页就能拿到主要字段,不必再深入详情页。

核心代码结构大致是:

import requests from lxml import html headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", "Accept": "text/html,application/xhtml+xml", } resp = requests.get(url, headers=headers, timeout=15) resp.encoding = "utf-8" doc = html.fromstring(resp.text) # 提取商品行 rows = doc.xpath("//table[@class='price-table']/tbody/tr") for row in rows: name = row.xpath("./td[1]/a/text()") price_text = row.xpath("./td[3]/text()") ...

XPath 的text()函数提取文本时,经常遇到返回空列表的情况,原因多半是文本被嵌套在子标签里。稳妥的写法是用string(...)或者.//text()拼接。比如row.xpath("string(./td[1])")可以直接拿到该 td 下的所有文本内容,虽然有时会带多余空白,但清洗一下就好。

还有一个容易翻车的地方是编码。农产品网站很多还是 GBK 或者 GB2312 编码,直接resp.text会出现乱码。我的做法是先判断resp.encoding,如果是 gb2312 之类的就用resp.content.decode("gbk", errors="ignore")处理。注意 Python 的标准库里没有 gb2312 这个编码名,要用gbk兼容它。

3.2 动态页面的处理:Selenium 与接口直取

现在很多小型电商网站也在升级前端框架,页面改成了 Ajax 动态加载。遇到这种页面,直接用 requests 拿到的是空壳 HTML,里面根本没有数据。

应对办法有两个方向。第一个是用 Selenium 模拟浏览器,这个方法最无脑但最重,启动浏览器实例消耗资源很大,多并发场景基本跑不起来。第二个是抓包找接口,直接请求后端的 JSON 数据接口,这个方法我强烈推荐优先尝试。

找接口的办法是在浏览器按 F12 打开开发者工具,切到 Network 面板,重新刷新页面,观察哪些 XHR 请求返回的是 JSON 数据。找到之后直接用 requests 模拟这个请求,注意把请求头里的Referer和X-Requested-With带上,很多站点校验这两个字段。

接口直取的成功率高、速度快,返回的 JSON 直接json.loads()就可以处理,省掉了 XPath 解析的麻烦。唯一的风险是接口地址和参数可能会变,这个只能靠定期巡检来应对,检查发现解析失败就及时更新配置。

3.3 反爬应对:请求头、频率、验证码

反爬这个点我单独拿出来讲,因为太多人一上来就想着搞代理池、搞验证码识别,忽略了最基本的请求头伪装。农产品行情类网站的反爬通常不会太强,最基础的检查就是 User-Agent 和访问频率。

我建议先做这几件事:

  • 随机换 User-Agent,不要一直用一个默认的 Python-requests 标识。
  • 请求间隔随机化,用time.sleep(random.uniform(2, 5)),避免固定频率被发现。
  • 尽量模拟正常浏览路径,先访问首页再访问数据页,不要上来直接请求深链。
  • 如果目标站点要求 Cookie,先用 requests.Session() 保序访问,让 Server 认为你是同一个会话。

验证码这块我的态度是尽量规避,而不是硬刚。如果某个目标站频繁弹验证码,先降低采集频率,或者改到凌晨低峰期采集。很多学生项目在验证码识别上交了大量学费,其实绕开问题的成本远低于解决问题。

3.4 数据清洗与入库的细节

清洗阶段最耗时,也最容易出问题。农产品数据有几个典型脏数据来源:数字中混入全角字符、单位不统一(元/斤 vs 元/公斤)、价格字段被写成"暂无"或"--"、不同产地同一品种的名称不一致等。

我的清洗函数大致逻辑是:

def clean_price(raw_text): if not raw_text: return None raw_text = raw_text.replace("元", "").replace(" ", "") if "--" in raw_text or "暂无" in raw_text: return None try: return float(raw_text) except ValueError: return None

这里最重要的是"失败返回 None 而不是抛异常"。爬虫数据量大,一条脏数据不应该中断整个任务。记录清洗日志,最后统计一下有多少条被清洗掉,如果比例异常高再回查解析规则。

写入数据库时,我建议用update_or_create而不是先查再写。update_or_create是 Django ORM 自带的方法,如果记录存在就更新,不存在就创建,配合唯一约束使用效果很好。以价格行情表为例,唯一约束可以设在"商品 + 日期 + 来源站点"这个组合上,这样同一来源同一天的数据不会重复。

4. 大数据链路上的数据流转与分析

4.1 采集数据如何进入大数据分析流程

爬虫采集并清洗后的数据最终落到 MySQL,这是业务库。但如果要做大数据分析,直接分析 MySQL 里的数据不是不行,可是有隐患:分析任务通常比较重,比如算 30 天价格均值、月度价格波动率、产地供货量排名,这些查询如果直接在业务库上跑,会拖累线上接口的响应速度。

我的做法是把分析链路拆开:MySQL 存实时业务数据,同时通过定时任务每天把明细数据同步到大数据平台(比如 Hadoop Hive 或者 ClickHouse),分析查询只在大数据平台执行。同步方式最简单的是先导出 CSV,再用脚本加载到目标表。数据量如果达到百万级以上,再考虑用 Sqoop 或者 DataX 之类的工具。

分析结果如果还要展示回 Django 前台,就把计算结果写回一张单独的结果表。前台只读结果表,不直接跑聚合查询。这个分层方式在大数据量下几乎是必须的,否则随着数据积累,一次查询几十秒的体验谁也接受不了。

4.2 数据库选型:MySQL、MongoDB、ClickHouse 的取舍

我在不同阶段用过不同的存储方案,在这里整理一下各自的定位,方便你选型。

MySQL 适合存结构化强、关系复杂的业务数据,比如商品、订单、用户。它的优势是生态成熟、事务支持好、Django ORM 无缝对接。劣势是海量明细细分数据的聚合查询性能一般,几百万条数据做 group by 就会开始吃力。

MongoDB 适合存原始解析前的 JSON 数据,schema 灵活,采集字段变了不用改表结构。但 MongoDB 不适合做复杂的关联查询,事务能力也比较弱,和 Django ORM 对接要多加一层。

ClickHouse 是分析型数据库,列式存储,做聚合查询非常快。一亿条数据做 group by 也能秒级返回。缺点是它是为分析而生的,不适合频繁的单条更新,也不适合当业务主库用。

我当时业务库用 MySQL,大数据分析库用 ClickHouse,原始数据暂存 MongoDB,三层各司其职。如果你只是课程设计级别的项目,数据量不大,MySQL 一份数据就够用,分析型数据库可以先不引入,但你要明白这个架构演进的方向。

4.3 简单的价格趋势与产地分布统计

大数据分析落到具体应用上,最直接的是价格趋势分析和产地分布统计。价格趋势用来给用户展示"过去 30 天某种蔬菜的价格走势",产地分布则展示"某商品的货源分别来自哪些省份、占比多少"。

这两个分析在 ClickHouse 里的 SQL 非常简单:

SELECT toDate(price_date) AS date, avg(avg_price) AS avg_price FROM price_daily_detail WHERE product_code = '01' AND price_date >= today() - 30 GROUP BY date ORDER BY date;

产地分布则是:

SELECT province, count() AS supply_cnt FROM price_daily_detail WHERE product_code = '01' AND price_date >= today() - 30 GROUP BY province ORDER BY supply_cnt DESC;

分析结果每天定时写入 Django 的结果表,前台页面直接读结果表渲染图表。这样用户看到的图是秒开的,底层的重型查询在凌晨就完成了。很多做可视化的人容易忽略这一点:前台的「快」不是靠优化查询,而是靠提前算好。

5. 常见问题与排障实录

5.1 爬虫任务卡死、请求超时的处理

我遇到最头疼的问题是爬虫任务"看起来在跑,实际上卡死了"。表现是 Celery Worker 进程存在,但任务状态一直没有更新,日志也没有新输出。原因通常是 requests 没有设置 timeout,对方服务器连接挂起,线程一直在等响应。

排查步骤是:

  1. 先看 Celery 日志最后一条输出是什么,能判断卡在哪个请求上。
  2. 检查该请求的目标 URL 在浏览器里能不能正常打开。
  3. 确认代码里是否所有请求都设置了 timeout。
  4. 用lsof -p <worker_pid>看这个进程当前打开的连接,能确认是否卡在某个 socket 上。

修法分两层,第一层是请求层设 timeout,第二层是任务层用信号超时兜底。我写了一个通用的抓取函数,所有请求默认 timeout=15,并在函数外层包了超时保护,超过 30 秒直接抛异常让任务失败,失败后自动记录到任务表,方便人工排查。

5.2 重复数据与增量采集

重复数据是爬虫项目最容易出问题的点。我刚开始做的时候也碰到过,一天跑下来价格表多了 30% 重复记录,后来靠update_or_create配合唯一约束解决了。

增量采集的思路稍微复杂一点。农产品价格是按天更新的,理论上每天只需采集一次当天的数据。但有些站点当天数据更新不及时,比如上午显示的还是昨天的价格,下午才更新成今天的。我的策略是每天分时段采多次,具体采集时段和站点更新时间对齐,但写入时始终用唯一约束去重,最终保证库里只有一份当日数据。

重跑历史数据的情况也要考虑。如果某天采集任务挂了,第二天需要补采前一天的数据,所以在写解析逻辑时要保留"指定日期"的入参,这样一个函数既能采当天数据也能补历史数据。

5.3 IP 封禁与代理池管理

访问频率过快被封 IP 几乎每个人都遇到过。封 IP 的迹象是本来正常的请求突然返回 403 或者跳转到验证码页面,而且用浏览器访问同一页面又是正常的。

规避办法从简单到复杂排列:调低频率、使用代理、构建代理池。对于这个项目,我先调低了频率,每天的采集任务分散到 3~4 个时段执行,不要集中跑完,效果已经好了很多。如果确实需要高频采集,就搭建一个代理池,用 Redis 维护一批代理 IP,每次请求前从池里随机取一个,失效就剔除。

代理池虽好,但不要迷信代理。农产品行情类网站的数据更新频率本身不高,完全没必要把采集频率拉到秒级,慢一点采集反而更稳,被封的概率大幅降低。稳定压倒一切。

5.4 Django 数据库连接池与并发写入

当爬虫任务并发提高、入库频率上来之后,Django ORM 的数据库连接管理会成为一个瓶颈。Django 默认的 ORM 连接是"请求时创建、请求结束关闭",在爬虫这种高并发写入场景下频繁创建连接的成本非常高,容易出现连接数耗尽的问题。

我用了django-db-connection-pool来改造成连接池模式:

DATABASES = { "default": { "ENGINE": "pool", ... "POOL_OPTIONS": { "POOL_SIZE": 10, "MAX_OVERFLOW": 10, "RECYCLE": 600, }, } }

POOL_SIZE 是核心连接数,MAX_OVERFLOW 是在高峰期额外创建的连接数,RECYCLE 是连接回收时间。不要一上来就调很大,连接数太大会反过来拖垮数据库。同时并发写入时要注意用transaction.atomic()控制事务边界,避免一部分数据写入成功另一部分失败导致的数据不一致。

5.5 常用排障指令速查

这里整理一下我日常排查爬虫问题会用到的指令,很基础但很实用:

# 查看 Celery Worker 日志 tail -f /var/log/celery/worker.log # 查询最近一次任务执行时间和状态 python manage.py shell -c "from apps.crawler.models import CrawlTask; print(list(CrawlTask.objects.order_by('-create_time')[:5].values_list('name','status','create_time')))" # 查看 Redis 队列中积压的任务数 redis-cli llen celery # 查看当前是否有进程还在抓取数据 ps -ef | grep celery

6. 部署链路与后续扩展的几点经验

6.1 从开发到生产的部署要点

开发环境下爬虫和 Django 跑在同一台机器没问题,但生产环境我建议至少拆成两台:一台跑 Django Web 服务,一台专门跑爬虫 Worker。原因很简单,爬虫对带宽和 CPU 的占用波动很大,跟 Web 服务抢资源会导致页面响应变慢。

部署的大致链路是:代码走 Git 仓库,Web 服务用 Gunicorn + Nginx,爬虫 Worker 用 Supervisor 托管。定时调度由 Django-celery-beat 负责,Beat 进程和 Worker 进程分开启动,配置文件用.env管理不同环境下的参数。

采集任务的启动要做好"手动触发优先"的设计。也就是说,上线初期不要完全依赖定时任务,每一步操作先手动跑一遍,确认无误后再挂到定时任务上。我第一次部署就是因为 Beet 配置写错了时间,凌晨采集任务根本没跑,一觉醒来数据全是空的。

6.2 日志、监控与任务重跑

日志的重要性怎么强调都不过分。爬虫不像是 Web 接口,出了问题用户会立刻感知,爬虫很多问题是"悄悄发生"的:任务跑了但解析失败、数据入库了但字段全是空、目标站点改版了 XPath 全部失效。如果没有完整的日志和状态监控,这些问题会积累到不可收拾才被发现。

我的做法是三重保障:任务表记录每次任务的运行摘要、日志文件记录详细错误堆栈、简单告警通过邮件或者钉钉机器人推送到工作群。任务重跑功能则设计成可挑选任务时间和任务名称,一键重跑指定时间段的失败任务,不需要改代码。

6.3 后续扩展:爬虫管理平台化

做到最后,我认为爬虫模块不应该只是一个隐藏在 Django 内部的功能,它可以演进为一个小型的管理平台。具体来说,可以做采集源配置管理,目标站点的 URL、XPath 规则、采集频率都要支持后台配置;采集任务的启停、重跑也要能在管理页面点按钮完成;数据监控要能从后台直接看到采集量曲线、失败率曲线。

这个进化路线的核心是"把规则从代码中抽离出来"。当爬虫规则写死在代码里时,每次目标站点改版都要发版上线;当规则变成数据库配置后,运维人员改配置就能完成调整。数据采集规模化之后,这条路几乎是必然要走。

我在实际开发中最深的体会是:爬虫的真正难点从来不是你抓不抓得到数据,而是抓到之后能不能稳定、干净、及时地流转到下游。前面花在数据库设计、任务调度、异常处理上的时间,后面都会加倍还给你。你可能会觉得这些事不够"酷",但生产环境里决定一个数据采集系统能不能长期跑的,恰恰就是这些不酷的细节。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询