ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Python智慧供应链物流新闻聚合系统源码:Scrapy多爬虫与Destoon CMS数据落库实战

Python智慧供应链物流新闻聚合系统源码:Scrapy多爬虫与Destoon CMS数据落库实战 简介这份源码面向智慧供应链与智慧物流方向的学习者和开发者提供一套基于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, pagesargs.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、steelhomespiders 目录下五个文件不是随便拆的。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_timeitems.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包里有四个 SQLdestoon_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( host127.0.0.1, userroot, password***, databasedestoon, charsetutf8mb4 ) 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收尾。参数说明charsetutf8mb4必须写物流新闻里常有生僻字和 emojiutf8 会报错。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连接串加 charsetSQL 脚本导入前确认文件编码。Destoon 老版本默认可能是 utf8 或 gbk导入脚本时如果编码不匹配中文直接变问号。连接串里charsetutf8mb4只是客户端侧服务端表结构也得是 utf8mb4。导入 SQL 前用file -i xxx.sql看编码必要时iconv转一遍。4.3 现象跑几十页后被封 IP 或返回 403。原因并发过高、无 UA 轮换、无延迟。解决降 CONCURRENT_REQUESTS开 DOWNLOAD_DELAYmiddlewares 里加随机 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 或独立 configspider 只管抓管道只管写。从那以后我每次接新采集项目都强制先把配置抽出来再写第一行爬虫代码。这套源码的 constant.py 和 settings.py 已经做了这层分离接手时别再把配置塞回去。希望帮到你。本文还有配套的精品资源点击获取
返回列表