Python爬虫实战:构建企业级的招投标信息监控工具,多页动态数据抓取,实现去重增量更新(附SQLite持久化存储)!

㊙️本期内容已收录至专栏《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 构建一个企业级的招投标信息监控工具。通过 requestsBeautifulSoup 实现多页动态数据抓取,结合 SQLite 数据库实现去重增量更新 ,并加入关键词过滤机制。
读完本文,你将获得

  • 一套完整的增量爬虫架构模板(抓取 -> 比对 -> 存储)。
  • 掌握如何通过数据指纹(Hash)判断信息更新,避免重复推送。
  • 学会编写具备自我保护意识(异常处理与限速)的专业代码。

2️⃣ 背景与需求(Why)

在政企采购、工程建设领域,招投标公告通常散落在各大政府门户网站。

  • 痛点:信息量大、更新频率高、搜索功能简陋。

  • 目标字段

    1. project_id:唯一标识(通常从 URL 或 HTML 属性中提取)。
    2. title:公告标题(用于关键词检索)。
    3. area:所属地区。
    4. publish_date:发布日期(用于判断是否为新数据)。
    5. detail_url:详情页链接。

3️⃣ 合规与注意事项(必写)

作为资深爬虫爱好者,我们要优雅且克制地获取数据:

  1. 遵守 Robots 协议 :优先检查目标站点的 /robots.txt,避开禁止抓取的目录。
  2. 频率控制:建议单次请求间隔设置在 1-3 秒,避免对政务服务器造成瞬时高并发压力。
  3. 合规性 :本教程仅用于学术交流及公开信息抓取,严禁采集任何涉及非公开、需要登录授权或个人隐私的数据。
  4. 非侵入式:不绕过任何验证码,不尝试攻击后台接口。

4️⃣ 技术选型与整体流程(What/How)

本次任务属于典型的静态/半动态网页爬取,流程如下:

  • 技术栈requests (请求) + BeautifulSoup4 (解析) + SQLite3 (增量存储)。

  • 流程图

    1. Init: 初始化数据库,加载已抓取的 ID 列表。
    2. Fetch: 循环遍历多页列表页。
    3. Parse: 提取公告列表,进行关键词匹配。
    4. Check: 比对数据库,若是新 ID 且匹配关键词,则标记为"新增"。
    5. 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

🔟 常见问题与排错(强烈建议写)

  1. 403 Forbidden

    • 原因:你的 IP 访问频率过快或缺少必要的 Cookie。
    • 对策 :降低抓取频率,或使用 requests.Session() 保持会话。
  2. 抓取到空列表

    • 原因:数据可能是通过 AJAX 异步加载的。
    • 对策:按 F12 检查 Network 选项卡,寻找 XHR 接口,直接爬取 JSON 数据。
  3. 乱码问题

    • 原因:网页使用 GBK 编码。
    • 对策response.encoding = response.apparent_encoding

1️⃣1️⃣ 进阶优化

  • 关键词矩阵 :支持从 keywords.txt 读取多个关键词。

  • 企业微信/钉钉提醒

    python 复制代码
    def 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 协议的前提下使用相关技术。严禁将技术用于任何非法用途或侵害他人权益的行为。技术无罪,责任在人!!!

相关推荐
喵手24 分钟前
Python爬虫实战:增量爬虫实战 - 利用 HTTP 缓存机制实现“极致减负”(附CSV导出 + SQLite持久化存储)!
爬虫·python·爬虫实战·零基础python爬虫教学·增量爬虫·http缓存机制·极致减负
一个处女座的程序猿O(∩_∩)O42 分钟前
Python异常处理完全指南:KeyError、TypeError、ValueError深度解析
开发语言·python
was17243 分钟前
使用 Python 脚本一键上传图片到兰空图床并自动复制链接
python·api上传·自建图床·一键脚本
好学且牛逼的马1 小时前
从“Oak”到“虚拟线程”:JDK 1.0到25演进全记录与核心知识点详解a
java·开发语言·python
shangjian0071 小时前
Python基础-环境安装-Anaconda配置虚拟环境
开发语言·python
codeJinger1 小时前
【Python】函数
开发语言·python
geovindu2 小时前
python: Command Pattern
开发语言·python·命令模式
曲幽2 小时前
FastAPI实战:WebSocket长连接保持与心跳机制,从入门到填坑
javascript·python·websocket·keep-alive·fastapi·heartbeat·connection
好学且牛逼的马4 小时前
从“混沌初开”到“有序统一”:Java集合框架发展历程与核心知识点详解
前端·数据库·python
a1117764 小时前
快速制作 虚拟形象项目 MotionPNGTuber
python·live2d