㊙️本期内容已收录至专栏《Python爬虫实战》,持续完善知识体系与项目实战,建议先订阅收藏,后续查阅更方便~持续更新中!
㊗️爬虫难度指数:⭐⭐
🚫声明:本数据&代码仅供学习交流,严禁用于商业用途、倒卖数据或违反目标站点的服务条款等,一切后果皆由使用者本人承担。公开榜单数据一般允许访问,但请务必遵守"君子协议",技术无罪,责任在人。

全文目录:
-
-
- [🌟 开篇语](#🌟 开篇语)
- [1️⃣ 标题 + 摘要(Abstract)](#1️⃣ 标题 + 摘要(Abstract))
- [2️⃣ 背景与需求(Why)](#2️⃣ 背景与需求(Why))
- [3️⃣ 合规与注意事项(必写)](#3️⃣ 合规与注意事项(必写))
- [4️⃣ 技术选型与整体流程(What/How)](#4️⃣ 技术选型与整体流程(What/How))
- [5️⃣ 环境准备与依赖安装(可复现)](#5️⃣ 环境准备与依赖安装(可复现))
- [6️⃣ 核心实现:请求层(Fetcher)](#6️⃣ 核心实现:请求层(Fetcher))
- [7️⃣ 核心实现:解析层(Parser)](#7️⃣ 核心实现:解析层(Parser))
- [8️⃣ 数据存储与导出(Storage)](#8️⃣ 数据存储与导出(Storage))
- [9️⃣ 运行方式与结果展示(必写)](#9️⃣ 运行方式与结果展示(必写))
- [🔟 常见问题与排错(强烈建议写)](#🔟 常见问题与排错(强烈建议写))
- [1️⃣1️⃣ 进阶优化](#1️⃣1️⃣ 进阶优化)
- [1️⃣2️⃣ 总结与延伸阅读](#1️⃣2️⃣ 总结与延伸阅读)
- [🌟 文末](#🌟 文末)
-
- [📌 专栏持续更新中|建议收藏 + 订阅](#📌 专栏持续更新中|建议收藏 + 订阅)
- [✅ 互动征集](#✅ 互动征集)
-
🌟 开篇语
哈喽,各位小伙伴们你们好呀~我是【喵手】。
运营社区: C站 / 掘金 / 腾讯云 / 阿里云 / 华为云 / 51CTO
欢迎大家常来逛逛,一起学习,一起进步~🌟
我长期专注 Python 爬虫工程化实战 ,主理专栏 《Python爬虫实战》:从采集策略 到反爬对抗 ,从数据清洗 到分布式调度 ,持续输出可复用的方法论与可落地案例。内容主打一个"能跑、能用、能扩展 ",让数据价值真正做到------抓得到、洗得净、用得上。
📌 专栏食用指南(建议收藏)
- ✅ 入门基础:环境搭建 / 请求与解析 / 数据落库
- ✅ 进阶提升:登录鉴权 / 动态渲染 / 反爬对抗
- ✅ 工程实战:异步并发 / 分布式调度 / 监控与容错
- ✅ 项目落地:数据治理 / 可视化分析 / 场景化应用
📣 专栏推广时间 :如果你想系统学爬虫,而不是碎片化东拼西凑,欢迎订阅/关注专栏👉《Python爬虫实战》👈
💕订阅后更新会优先推送,按目录学习更高效💯~
1️⃣ 标题 + 摘要(Abstract)
标题 :构建基于 Python 的招投标情报系统:多页增量采集与关键词预警方案
摘要 :本文将手把手教你使用 Python 构建一个企业级的招投标信息监控工具。通过 requests 与 BeautifulSoup 实现多页动态数据抓取,结合 SQLite 数据库实现去重增量更新 ,并加入关键词过滤机制。
读完本文,你将获得:
- 一套完整的增量爬虫架构模板(抓取 -> 比对 -> 存储)。
- 掌握如何通过数据指纹(Hash)判断信息更新,避免重复推送。
- 学会编写具备自我保护意识(异常处理与限速)的专业代码。
2️⃣ 背景与需求(Why)
在政企采购、工程建设领域,招投标公告通常散落在各大政府门户网站。
-
痛点:信息量大、更新频率高、搜索功能简陋。
-
目标字段:
project_id:唯一标识(通常从 URL 或 HTML 属性中提取)。title:公告标题(用于关键词检索)。area:所属地区。publish_date:发布日期(用于判断是否为新数据)。detail_url:详情页链接。
3️⃣ 合规与注意事项(必写)
作为资深爬虫爱好者,我们要优雅且克制地获取数据:
- 遵守 Robots 协议 :优先检查目标站点的
/robots.txt,避开禁止抓取的目录。 - 频率控制:建议单次请求间隔设置在 1-3 秒,避免对政务服务器造成瞬时高并发压力。
- 合规性 :本教程仅用于学术交流及公开信息抓取,严禁采集任何涉及非公开、需要登录授权或个人隐私的数据。
- 非侵入式:不绕过任何验证码,不尝试攻击后台接口。
4️⃣ 技术选型与整体流程(What/How)
本次任务属于典型的静态/半动态网页爬取,流程如下:
-
技术栈 :
requests(请求) +BeautifulSoup4(解析) +SQLite3(增量存储)。 -
流程图:
- Init: 初始化数据库,加载已抓取的 ID 列表。
- Fetch: 循环遍历多页列表页。
- Parse: 提取公告列表,进行关键词匹配。
- Check: 比对数据库,若是新 ID 且匹配关键词,则标记为"新增"。
- Save/Alert: 存入数据库并触发提醒。
5️⃣ 环境准备与依赖安装(可复现)
推荐使用 Python 3.9+。
项目结构推荐:
text
TenderMonitor/
├── data/ # 存放数据库
├── logs/ # 存放运行日志
├── tender_crawler.py # 主程序文件
└── requirements.txt # 依赖清单
安装依赖:
bash
pip install requests beautifulsoup4 loguru
6️⃣ 核心实现:请求层(Fetcher)
我们要像"真人"一样去访问。
python
import requests
from loguru import logger
import time
class TenderFetcher:
def __init__(self):
self.headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36",
"Referer": "https://www.example-tender.gov.cn/"
}
self.timeout = 15
def fetch_page(self, url, params=None):
try:
# 引入退避机制,如果失败重试 3 次
for i in range(3):
response = requests.get(url, headers=self.headers, params=params, timeout=self.timeout)
if response.status_code == 200:
response.encoding = 'utf-8' # 显式指定编码,防止乱码
return response.text
elif response.status_code == 429:
logger.warning("Triggered Rate Limit. Sleeping...")
time.sleep(10 * (i + 1))
return None
except Exception as e:
logger.error(f"Request failed: {e}")
return None
7️⃣ 核心实现:解析层(Parser)
处理缺失字段是解析层的灵魂。
python
from bs4 import BeautifulSoup
def parse_list_page(html_content):
soup = BeautifulSoup(html_content, 'html.parser')
items = []
# 假设列表在 class 为 list-item 的 li 标签中
rows = soup.find_all('li', class_='list-item')
for row in rows:
try:
title_node = row.find('a')
date_node = row.find('span', class_='date')
# 容错处理:如果节点不存在则跳过
if not title_node or not date_node:
continue
item = {
'title': title_node.get_text(strip=True),
'link': "https://www.example-tender.gov.cn" + title_node['href'],
'publish_date': date_node.get_text(strip=True),
# 提取链接中的 ID 作为唯一标识
'item_id': title_node['href'].split('/')[-1].replace('.html', '')
}
items.append(item)
except Exception as e:
logger.debug(f"Parsing single row error: {e}")
return items
8️⃣ 数据存储与导出(Storage)
使用 SQLite 进行增量去重。这是"哨兵"的核心,通过 INSERT OR IGNORE 确保不重复。
| Field Name | Type | Example |
|---|---|---|
item_id |
TEXT (PK) | "20231124001" |
title |
TEXT | "XX省办公用品采购项目" |
link |
TEXT | "http://..." |
is_notified |
INTEGER | 0 / 1 |
python
import sqlite3
def init_db():
conn = sqlite3.connect('data/tenders.db')
cursor = conn.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS tender_list (
item_id TEXT PRIMARY KEY,
title TEXT,
link TEXT,
publish_date TEXT,
create_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
''')
conn.commit()
return conn
def save_to_db(conn, item):
cursor = conn.cursor()
try:
cursor.execute(
"INSERT INTO tender_list (item_id, title, link, publish_date) VALUES (?, ?, ?, ?)",
(item['item_id'], item['title'], item['link'], item['publish_date'])
)
conn.commit()
return True # 表示是新增数据
except sqlite3.IntegrityError:
return False # 数据已存在
9️⃣ 运行方式与结果展示(必写)
启动命令:
bash
python tender_crawler.py --keyword "医疗" --pages 5
运行逻辑预览:
python
# 伪代码片段
for page in range(1, 6):
html = fetcher.fetch_page(base_url, params={'page': page})
items = parse_list_page(html)
for item in items:
if "医疗" in item['title']: # 关键词过滤
is_new = save_to_db(conn, item)
if is_new:
print(f"🔥发现新增匹配公告: {item['title']}")
示例输出(Execution Result):
| item_id | title | publish_date |
|---|---|---|
| 558921 | 2023年医疗器械采购招标 | 2023-11-24 |
| 558925 | 某医院数字化影像系统项目 | 2023-11-24 |
🔟 常见问题与排错(强烈建议写)
-
403 Forbidden:
- 原因:你的 IP 访问频率过快或缺少必要的 Cookie。
- 对策 :降低抓取频率,或使用
requests.Session()保持会话。
-
抓取到空列表:
- 原因:数据可能是通过 AJAX 异步加载的。
- 对策:按 F12 检查 Network 选项卡,寻找 XHR 接口,直接爬取 JSON 数据。
-
乱码问题:
- 原因:网页使用 GBK 编码。
- 对策 :
response.encoding = response.apparent_encoding。
1️⃣1️⃣ 进阶优化
-
关键词矩阵 :支持从
keywords.txt读取多个关键词。 -
企业微信/钉钉提醒:
pythondef send_dingtalk(msg): # 你的钉钉机器人 Webhook 地址 url = "https://oapi.dingtalk.com/robot/send?access_token=..." data = {"msgtype": "text", "text": {"content": f"招标提醒: {msg}"}} requests.post(url, json=data) -
定时任务 :使用
schedule库,每 30 分钟检查一次。
1️⃣2️⃣ 总结与延伸阅读
通过本文,我们从零构建了一个具备增量比对能力的爬虫。这不仅是一个技术练习,更是一个能真正投入生产的工具。
复盘:
- 我们解决了重复采集的问题。
- 我们实现了自动化过滤关键情报。
下一步建议:
- 学习 Playwright:如果遇到重度加密或 JavaScript 渲染的站点,Playwright 是神器。
- 数据可视化:将抓取到的招标金额、地区分布用 Matplotlib 做成 English 标签的看板,直观分析市场趋势。
🌟 文末
好啦~以上就是本期 《Python爬虫实战》的全部内容啦!如果你在实践过程中遇到任何疑问,欢迎在评论区留言交流,我看到都会尽量回复~咱们下期见!
小伙伴们在批阅的过程中,如果觉得文章不错,欢迎点赞、收藏、关注哦~
三连就是对我写作道路上最好的鼓励与支持! ❤️🔥
📌 专栏持续更新中|建议收藏 + 订阅
专栏 👉 《Python爬虫实战》,我会按照"入门 → 进阶 → 工程化 → 项目落地"的路线持续更新,争取让每一篇都做到:
✅ 讲得清楚(原理)|✅ 跑得起来(代码)|✅ 用得上(场景)|✅ 扛得住(工程化)
📣 想系统提升的小伙伴:强烈建议先订阅专栏,再按目录顺序学习,效率会高很多~

✅ 互动征集
想让我把【某站点/某反爬/某验证码/某分布式方案】写成专栏实战?
评论区留言告诉我你的需求,我会优先安排更新 ✅
⭐️ 若喜欢我,就请关注我叭~(更新不迷路)
⭐️ 若对你有用,就请点赞支持一下叭~(给我一点点动力)
⭐️ 若有疑问,就请评论留言告诉我叭~(我会补坑 & 更新迭代)

免责声明:本文仅用于学习与技术研究,请在合法合规、遵守站点规则与 Robots 协议的前提下使用相关技术。严禁将技术用于任何非法用途或侵害他人权益的行为。技术无罪,责任在人!!!