简介:这份源码面向智慧供应链与智慧物流方向的学习者和开发者,提供一套基于Python的新闻聚合系统完整实现,可用于课程设计、毕业设计或二次开发参考。项目围绕供应链物流资讯的自动抓取、解析、入库与展示展开,借助Scrapy框架与多源爬虫模块,帮助读者理解从数据采集到Web呈现的完整链路。压缩包共31个文件,约125KB,其中24个py文件承担爬虫、数据处理、管道与配置等核心逻辑,4个sql脚本负责数据库表结构与数据初始化,另含1个docx业务数据说明文档、1个cfg爬虫配置及1个txt说明文件,目录按spiders、api、utils、baseitems等模块划分,结构清晰。目前已有263人学习下载。读者可从中获取可运行的聚合系统骨架、多站点爬虫示例、数据库脚本与模块化目录组织思路,便于快速搭建同类信息聚合平台并在此基础上扩展功能。
1. 从一堆散乱文件到能跑的物流新闻聚合:这套 Python 源码到底解决什么问题
做供应链和物流信息的人大概都有过这种体验:每天早上打开十几个网站,钢铁、物流、政策、企业案例各看一遍,手动复制粘贴到表格里,一天光收集信息就耗掉两小时。这套基于 Python 的智慧供应链物流新闻聚合系统源码,就是冲着这个场景来的。它用 Scrapy 做抓取骨架,把物流新闻、政策法规、企业案例分析、钢铁行情等几个来源的资讯统一采集、清洗、入库,再通过 SQL 脚本建好的表结构对外提供查询。整个包 29 个文件,其中 23 个 Python 源文件、4 个 SQL 脚本、1 个 Word 业务数据文档、1 个 scrapy.cfg 配置。适合谁?一是想学 Scrapy 多爬虫工程化组织的人,二是做物流信息化、供应链平台、行业资讯站,需要一个能直接改改就上手的采集底座。它不是成品 SaaS,是一套能拆开看、能改、能接自己数据库的工程源码。
2. 拆开 wuliu_news 目录:Scrapy 工程结构与 5 个爬虫的分工逻辑
2.1 从 main.py 到 spiders:一次请求的完整链路
先看入口。main.py 不是 Scrapy 默认的爬虫启动方式,它更像一个调度壳,配合 args_parser.py 解析命令行参数,决定这次跑哪个爬虫、跑多少页、是否写库。常见做法是用scrapy crawl直接起,但这套代码把参数抽出来,方便定时任务里传不同参数。
# main.py 简化逻辑示意 from scrapy.crawler import CrawlerProcess from scrapy.utils.project import get_project_settings from args_parser import parse_args def run(): args = parse_args() # 解析 --spider --pages --dry-run settings = get_project_settings() process = CrawlerProcess(settings) process.crawl(args.spider, pages=args.pages) process.start() if __name__ == "__main__": run()逻辑说明:parse_args()把爬虫名、页数、是否只解析不入库这些开关收进来;get_project_settings()读取 settings.py 里的并发、延迟、管道配置;CrawlerProcess负责启动。参数说明:--spider对应 spiders 目录下的模块名,--pages控制翻页深度,--dry-run在调试时只打印不落库。这样改的好处是,同一套代码既能手动跑,也能塞进 crontab。
2.2 五个 spider 各管一摊:zcfgNews、wlbzNews、wuliuNews、qyalfx、steelhome
spiders 目录下五个文件不是随便拆的。zcfgNews.py 抓政策法规类,wlbzNews.py 对应物流标准或物流保障类栏目,wuliuNews.py 是综合物流新闻,qyalfx.py 做企业案例分析,steelhome.py 盯钢铁行情。每个 spider 的start_urls、parse方法、翻页规则都独立,互不干扰。
# spiders/wuliuNews.py 结构示意 import scrapy from wuliu_news.items import NewsItem class WuliuNewsSpider(scrapy.Spider): name = "wuliuNews" allowed_domains = ["example-logistics.com"] start_urls = ["https://example-logistics.com/news/"] def parse(self, response): for li in response.css("ul.news-list li"): item = NewsItem() item["title"] = li.css("a::text").get(default="").strip() item["url"] = response.urljoin(li.css("a::attr(href)").get()) item["source"] = self.name yield item next_page = response.css("a.next::attr(href)").get() if next_page: yield response.follow(next_page, self.parse)逻辑说明:parse先抽列表页的标题和链接,封装成NewsItem,再找下一页。参数说明:allowed_domains限制域名防止跑偏,source字段标记来源爬虫,方便后面按来源筛选。这里有个细节,default=""是防止某个 li 没有 a 标签时抛异常,属于血泪经验——线上跑的时候一个空节点就能让整批中断。
2.3 items 与 baseitems:字段定义为什么要分两层
items.py 和 baseitems/baseitem.py 分两层,是这套代码比较讲究的地方。baseitem.py 定义公共字段,比如title、url、publish_time、source、crawl_time;items.py 里的NewsItem继承它,再补业务字段。这样做的理由是,五个爬虫抓的字段不完全一样,钢铁行情可能要price,企业案例要company,但公共部分不该重复写五遍。
# baseitems/baseitem.py import scrapy class BaseNewsItem(scrapy.Item): title = scrapy.Field() url = scrapy.Field() publish_time = scrapy.Field() source = scrapy.Field() crawl_time = scrapy.Field() # items.py from baseitems.baseitem import BaseNewsItem class NewsItem(BaseNewsItem): content = scrapy.Field() category = scrapy.Field()逻辑说明:继承让字段复用,新增字段只写在子类。参数说明:crawl_time建议在 pipeline 里统一填,不要在 spider 里写死,否则时区容易乱。常见误用是把所有字段塞一个 item,结果五个爬虫互相污染,后面入库时字段对不上。
2.4 settings 与 middlewares:并发、延迟和反爬的平衡点
settings.py 里几个关键项决定这套代码能不能稳定跑。CONCURRENT_REQUESTS默认别开太高,物流类站点多半扛不住;DOWNLOAD_DELAY给 1 到 2 秒;ROBOTSTXT_OBEY看目标站态度;ITEM_PIPELINES里挂上清洗和入库管道。middlewares.py 一般放随机 UA 和重试逻辑。
# settings.py 关键片段 CONCURRENT_REQUESTS = 8 DOWNLOAD_DELAY = 1.5 RETRY_TIMES = 3 ITEM_PIPELINES = { "wuliu_news.pipelines.CleanPipeline": 300, "wuliu_news.pipelines.DbPipeline": 400, }逻辑说明:数字越小越稳但越慢,300/400 是管道执行顺序,数字小的先跑。参数说明:RETRY_TIMES配合 middlewares 里的重试中间件,遇到 502、超时自动重来。注意,DOWNLOAD_DELAY是每个域名之间的间隔,不是全局,多域名并发时实际压力比想象大。
3. 数据落库:4 个 SQL 脚本与 pipelines 的配合方式
3.1 destoon 系列 SQL 脚本说明这套系统对接的是什么 CMS
包里有四个 SQL:destoon_quote_data.sql、destoon_quote.sql、destoon_article_data_21.sql、destoon_article_21.sql。从命名看,这套系统是往 Destoon 这类 CMS 的数据库结构里灌数据——article 表存文章主表,article_data 存正文内容,quote 和 quote_data 对应行情报价。也就是说,采集端不自己造一套表,而是直接对齐已有 CMS 的表结构,采完就能在现有站点里显示。
| 脚本文件 | 对应表 | 用途 |
|---|---|---|
| destoon_article_21.sql | 文章主表 | 标题、分类、发布时间、来源 |
| destoon_article_data_21.sql | 文章内容表 | 正文 HTML、摘要 |
| destoon_quote.sql | 行情主表 | 品名、地区、报价单位 |
| destoon_quote_data.sql | 行情数据表 | 具体价格、涨跌、日期 |
这个设计的好处是省去二次开发,坏处是字段被 CMS 绑死,想加自定义字段得改表。常见做法是先在测试库跑一遍脚本,确认字符集和自增 ID 不冲突再上生产。
3.2 pipelines 里的清洗与入库:从 item 到 SQL 的最后一公里
pipelines.py 通常两个管道:一个清洗,一个入库。清洗管道去空白、统一时间格式、过滤重复标题;入库管道用数据库连接把 item 映射成 INSERT。
# pipelines.py 入库管道示意 import pymysql from itemadapter import ItemAdapter class DbPipeline: def open_spider(self, spider): self.conn = pymysql.connect( host="127.0.0.1", user="root", password="***", database="destoon", charset="utf8mb4" ) self.cur = self.conn.cursor() def process_item(self, item, spider): adapter = ItemAdapter(item) sql = "INSERT INTO destoon_article (title, url, addtime) VALUES (%s, %s, %s)" self.cur.execute(sql, ( adapter.get("title"), adapter.get("url"), adapter.get("publish_time") )) self.conn.commit() return item def close_spider(self, spider): self.cur.close() self.conn.close()逻辑说明:open_spider建连接,process_item逐条写,close_spider收尾。参数说明:charset="utf8mb4"必须写,物流新闻里常有生僻字和 emoji,utf8 会报错。addtime如果是 CMS 的时间戳字段,注意单位是秒还是毫秒,填错时间会显示成 1970 年。这里建议加INSERT IGNORE或先查重,否则重复跑会灌一堆重复数据。
3.3 data_process.py 与 constant.py:清洗规则和常量集中管理
data_process.py 放文本清洗函数,比如去 HTML 标签、去广告尾巴、统一日期格式。constant.py 放常量,比如来源映射、分类字典、数据库字段名。把这两块抽出来,是为了改规则时不用翻 spider。
# data_process.py 清洗函数示意 import re def clean_text(raw): if not raw: return "" text = re.sub(r"<[^>]+>", "", raw) # 去标签 text = re.sub(r"\s+", " ", text) # 合并空白 text = text.replace("转载请注明", "").strip() return text逻辑说明:正则去标签是最基础的一步,\s+合并多余空白。参数说明:replace那行是去掉常见转载声明,实际项目里可以按来源维护一个尾巴词表。注意,正则去标签对嵌套标签和 script 内容无效,正文抽取更稳的做法是用 readability 或 lxml 的 text_content。
4. 避坑与排查:这套源码跑起来最容易翻车的 5 个地方
4.1 现象:爬虫跑完数据库没数据。原因:管道没启用或 item 字段名对不上。解决:先看 settings 里 ITEM_PIPELINES 是否注释掉,再打印 item 确认 key 和 SQL 占位符一致。
这是最高频的翻车点。很多人改完 spider 直接跑,日志显示 crawled 200 条,数据库空空。先查ITEM_PIPELINES有没有被注释,再在process_item里加一行print(dict(item)),看字段名是不是publish_time而 SQL 里写的是pub_time。字段名对不上,pymysql 会直接抛 KeyError 或写空值。
4.2 现象:中文乱码或 emoji 报错。原因:数据库或连接字符集不是 utf8mb4。解决:建库建表统一 utf8mb4,连接串加 charset,SQL 脚本导入前确认文件编码。
Destoon 老版本默认可能是 utf8 或 gbk,导入脚本时如果编码不匹配,中文直接变问号。连接串里charset="utf8mb4"只是客户端侧,服务端表结构也得是 utf8mb4。导入 SQL 前用file -i xxx.sql看编码,必要时iconv转一遍。
4.3 现象:跑几十页后被封 IP 或返回 403。原因:并发过高、无 UA 轮换、无延迟。解决:降 CONCURRENT_REQUESTS,开 DOWNLOAD_DELAY,middlewares 里加随机 UA 和重试。
物流类站点很多是中小站,防护不强但也不傻。默认并发 16 加零延迟,几分钟就触发限流。把并发降到 4 到 8,延迟 1.5 到 2 秒,UA 池准备五六个,基本能稳住。注意,DOWNLOAD_DELAY对同一域名生效,多 spider 同时跑同一站时压力叠加。
4.4 现象:翻页翻到重复内容或死循环。原因:下一页选择器匹配到了当前页或空链接。解决:打印 next_page 值,加 URL 去重集合,限制最大页数。
有些站点的「下一页」在最后一页仍然指向自己,或者 href 是javascript:;。response.follow拿到这种链接会反复请求同一页。在 spider 里维护一个self.seen = set(),每次 yield 前判断 URL 是否已抓,再加if self.page >= max_pages: return兜底。
4.5 现象:定时任务里跑报 scrapy 命令找不到。原因:crontab 环境变量和虚拟环境不一致。解决:用绝对路径调 python,或在脚本里先 source 虚拟环境。
手动跑没问题,一进 crontab 就报scrapy: command not found,这是环境变量没带进去。稳妥做法是 crontab 里写/path/to/venv/bin/python /path/to/main.py --spider wuliuNews,用虚拟环境里的 python 直接执行,不依赖全局 scrapy 命令。
5. 二次开发与验证:把采集结果接进自己系统的两个实用技巧
5.1 用 api/addnews.py 做入库前的二次校验
api 目录下的 addnews.py 值得单独看。它像是采集端和业务端之间的一个接口层,可以在正式写库前做校验:标题长度、来源白名单、发布时间是否合理、是否已存在。与其让脏数据进库再清理,不如在入口拦一道。
# api/addnews.py 校验逻辑示意 from data_process import clean_text def validate(item): title = clean_text(item.get("title", "")) if len(title) < 6 or len(title) > 120: return False, "标题长度异常" if not item.get("url", "").startswith("http"): return False, "URL 非法" if item.get("source") not in ("wuliuNews", "zcfgNews", "steelhome"): return False, "来源不在白名单" return True, title逻辑说明:先清洗再校验,长度、URL、来源三道关。参数说明:标题下限 6 是过滤导航文字,上限 120 是防止把整段正文当标题。来源白名单按实际 spider 名维护。这个函数可以在 pipeline 里调,也可以单独跑批校验历史数据。
5.2 验证采集质量:三个必看的指标
跑完不是看条数就完事。第一看字段完整率,title、url、publish_time的空值比例,超过 10% 说明选择器有问题。第二看重复率,按 URL 或标题去重后还剩多少。第三看时间分布,如果publish_time全挤在同一天,多半是抓成了列表页的静态时间。
-- 字段完整率与重复率检查 SELECT COUNT(*) AS total, SUM(title IS NULL OR title = '') AS empty_title, SUM(url IS NULL OR url = '') AS empty_url, COUNT(DISTINCT url) AS distinct_url FROM destoon_article WHERE addtime > UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL 7 DAY));逻辑说明:一条 SQL 同时看总量、空值和去重后数量。参数说明:addtime按 CMS 实际字段名替换,时间窗口按需改。distinct_url明显小于 total 就说明有重复灌入,回头查 pipeline 有没有做去重。
5.3 一个我踩过的坑:别在 spider 里写死数据库配置
早期图省事,把数据库连接直接写在 spider 里,结果换环境时改了五六个文件。后来统一挪到 settings 或独立 config,spider 只管抓,管道只管写。从那以后我每次接新采集项目,都强制先把配置抽出来再写第一行爬虫代码。这套源码的 constant.py 和 settings.py 已经做了这层分离,接手时别再把配置塞回去。希望帮到你。
本文还有配套的精品资源,点击获取