Python爬虫实战:构建各地统计局数据发布板块的自动化索引爬虫(附CSV导出 + SQLite持久化存储)!

㊗️本期内容已收录至专栏《Python爬虫实战》,持续完善知识体系与项目实战,建议先订阅收藏,后续查阅更方便~

㊙️本期爬虫难度指数:⭐⭐⭐

🉐福利: 一次订阅后,专栏内的所有文章可永久免费看,持续更新中,保底1000+(篇)硬核实战内容。

全文目录:

      • [🌟 开篇语](#🌟 开篇语)
      • [1️⃣ 摘要(Abstract)](#1️⃣ 摘要(Abstract))
      • [2️⃣ 背景与需求(Why)](#2️⃣ 背景与需求(Why))
      • [3️⃣ 合规与注意事项(必写)](#3️⃣ 合规与注意事项(必写))
      • [4️⃣ 技术选型与整体流程(What/How)](#4️⃣ 技术选型与整体流程(What/How))
      • [5️⃣ 环境准备与L] ➔ [Save Index]](#5️⃣ 环境准备与L] ➔ [Save Index])
      • [5️⃣ 环境准备与依赖安装(可复现)](#5️⃣ 环境准备与依赖安装(可复现))
      • [6️⃣ 核心实现:URL 拼接与请求封装(Helper)](#6️⃣ 核心实现:URL 拼接与请求封装(Helper))
      • [7️⃣ 核心实现:列表与详情页解析(The Spider)](#7️⃣ 核心实现:列表与详情页解析(The Spider))
      • [8️⃣ 数据整合与导出(Controller)](#8️⃣ 数据整合与导出(Controller))
      • [9️⃣ 运行方式与结果展示(必写)](#9️⃣ 运行方式与结果展示(必写))
      • [🔟 常见问题与排错(Troubleshooting)](#🔟 常见问题与排错(Troubleshooting))
      • [1️⃣1️⃣ 进阶优化(Optional)](#1️⃣1️⃣ 进阶优化(Optional))
      • [1️⃣2️⃣ 总结与延伸阅读](#1️⃣2️⃣ 总结与延伸阅读)
      • [🌟 文末](#🌟 文末)
        • [✅ 专栏持续更新中|建议收藏 + 订阅](#✅ 专栏持续更新中|建议收藏 + 订阅)
        • [✅ 互动征集](#✅ 互动征集)
        • [✅ 免责声明](#✅ 免责声明)

🌟 开篇语

哈喽,各位小伙伴们你们好呀~我是【喵手】。

运营社区: C站 / 掘金 / 腾讯云 / 阿里云 / 华为云 / 51CTO

欢迎大家常来逛逛,一起学习,一起进步~🌟

我长期专注 Python 爬虫工程化实战 ,主理专栏 《Python爬虫实战》:从采集策略反爬对抗 ,从数据清洗分布式调度 ,持续输出可复用的方法论与可落地案例。内容主打一个"能跑、能用、能扩展 ",让数据价值真正做到------抓得到、洗得净、用得上

📌 专栏食用指南(建议收藏)

  • ✅ 入门基础:环境搭建 / 请求与解析 / 数据落库
  • ✅ 进阶提升:登录鉴权 / 动态渲染 / 反爬对抗
  • ✅ 工程实战:异步并发 / 分布式调度 / 监控与容错
  • ✅ 项目落地:数据治理 / 可视化分析 / 场景化应用

📣 专栏推广时间 :如果你想系统学爬虫,而不是碎片化东拼西凑,欢迎订阅专栏👉《Python爬虫实战》👈,一次订阅后,专栏内的所有文章可永久免费阅读,持续更新中。

💕订阅后更新会优先推送,按目录学习更高效💯~

1️⃣ 摘要(Abstract)

项目名称GovStats_Indexer ------ 区域性统计数据附件链接自动化清洗与索引系统

一句话说明

本项目针对典型的政府统计局"数据发布/统计公报"栏目(通常为静态多页列表),利用 Python 自动化遍历分页,深度解析列表页或详情页,提取指标名称、发布时间及核心附件下载链接,最终生成一份可供检索的资源索引 CSV。

读完能获得什么?

  1. URL 拼接艺术 :彻底搞懂相对路径(./report.doc)与绝对路径的转换逻辑。
  2. 详情页穿透:应对"列表页只有标题,下载链接在详情页正文里"的经典场景。
  3. SSL 证书绕过 :解决政府老旧网站常见的 SSLError 问题。

2️⃣ 背景与需求(Why)

为什么要爬这个?

  • 宏观研究:你需要收集某省过去 10 年的《国民经济和社会发展统计公报》来做 GDP 趋势分析。
  • 招投标/市调:寻找某市发布的"第七次人口普查"详细数据表(通常是 Excel 附件)。
  • 痛点 :手动一页页翻、一个个点开下载太慢了,而且不知道哪个文件坏了。我们需要先拿到链接清单

目标站点与字段

  • 目标:某市统计局 -> "数据发布" 或 "统计年鉴" 栏目。

  • 字段清单

    • title: 文件/指标标题(如:2023年1-9月主要经济指标)
    • publish_date: 发布时间(YYYY-MM-DD)
    • file_url: 核心资产,文件的直接下载地址
    • file_type: 文件扩展名(xls/pdf/doc)
    • source_page: 来源页面(方便回溯)

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

政府网站比较特殊,请务必保持敬畏之心:

  1. SSL 证书报错 :很多地方统计局网站的 HTTPS 证书可能过期了或者签发机构不被信任。在代码中我们需要显式地关闭验证(verify=False),但这不代表网站不安全,只是配置老旧。
  2. 低频采集 :这些服务器通常没有 CDN 加速,配置也就是几核 CPU。一定要慢! 建议每次请求间隔 2-3 秒。如果你把统计局网站搞崩了,后果很严重。
  3. 只读不写:只抓取公开数据,不要尝试扫描后台目录或进行 SQL 注入测试。

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

技术栈

  • requests:关闭 SSL 验证的神器。
  • BeautifulSoup:处理不规范 HTML(比如标签没闭合)比 XPath 容错率更高。
  • urllib.parse.urljoin本篇的 MVP 。用于解决 ../../images/data.xls 这种相对路径的拼接问题。

整体流程图

**Pagination Loop** \] ➔ \[**Get List HTML** \] ➔ \[**Extract Item Link** \] ➔ \[**Deep Dive (Request Detail Page)** \] ➔ \[**Find Attachment ``** \] ➔ \[**Construct Absolute URL** \] ➔ \[**Save Index**

5️⃣ 环境准备与L] ➔ [Save Index]

5️⃣ 环境准备与依赖安装(可复现)

项目结构

text 复制代码
GovStats/
├── output/
│   └── stats_index.csv     # 结果索引
├── spider.py               # 爬虫逻辑
└── requirements.txt

依赖安装

bash 复制代码
pip install requests beautifulsoup4 pandas loguru

6️⃣ 核心实现:URL 拼接与请求封装(Helper)

政府网站最让人头疼的就是链接拼接

比如当前页面是 http://xx.gov.cn/data/list.html,源码里的链接是 ./2023/report.xls

如果你直接拼,很容易拼错。必须用 urljoin

python 复制代码
import requests
from urllib.parse import urljoin
from loguru import logger
import urllib3

# 禁用安全请求警告(针对 verify=False)
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

class GovFetcher:
    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"
        }

    def fetch(self, url):
        """
        封装的请求方法,自动处理 SSL 错误和编码
        """
        try:
            # verify=False 是关键,绕过过期证书
            resp = requests.get(url, headers=self.headers, verify=False, timeout=15)
            
            # 自动检测编码(解决 GBK/UTF-8 混用问题)
            # 如果自动检测不准,可以尝试 resp.encoding = 'gbk' 或 'utf-8'
            resp.encoding = resp.apparent_encoding 
            
            return resp.text, resp.url
        except Exception as e:
            logger.error(f"Failed to fetch {url}: {e}")
            return None, None

    def make_absolute_url(self, base_url, relative_url):
        """
        将相对路径转为绝对路径
        """
        if not relative_url: return ""
        return urljoin(base_url, relative_url)

7️⃣ 核心实现:列表与详情页解析(The Spider)

这里我们假设一种最复杂的场景:

  1. **列表*只展示标题。
  2. 点击标题进入详情页
  3. **详情页正文底部有一个"附件下载:xxx.xls"。
python 复制代码
from bs4 import BeautifulSoup
import re
import time
import random

class StatsSpider:
    def __init__(self, base_list_url):
        self.fetcher = GovFetcher()
        self.base_list_url = base_list_url # e.g., "http://tjj.city.gov.cn/col/col123/index.html"

    def parse_list_page(self, html, current_url):
        """解析列表页,提取详情页链接"""
        soup = BeautifulSoup(html, "html.parser")
        items = []
        
        # 寻找列表项,这里需要根据实际网站结构调整选择器
        # 假设结构是 <ul classs="news_list"><li><a href="...">Title</a><span>Date</span></li></ul>
        # 这是一个非常通用的政府网站构
        li_tags = soup.select("ul li") 
        
        for li in li_tags:
            a_tag = li.find("a")
            date_span = li.find("span")
            
            if a_tag:
                title = a_tag.get_text(strip=True)
                relative_link = a_tag.get('href')
                
                # 拼接详情页 URL
                detail_url = self.fetcher.make_absolute_url(current_url, relative_link)
                
                # 提取日期
                pub_date = date_span.get_text(strip=True) if date_span else "Unknown"
                
                items.append({
                    "title": title,
                    "date": pub_date,
                    "detail_url": detail_url
                })
        return items

    def parse_detail_page_for_attachments(self, html, detail_url):
        """
        深入详情页,寻找 .xls, .pdf, .doc, .zip 等附件链接
        """
        soup = BeautifulSoup(html, "html.parser")
        attachments = []
        
        # 查找所有 a 标签
        all_links = soup.find_all("a")
        
        for link in all_links:
            href = link.get('href')
            text = link.get_text(strip=True)
            
            if not href: continue
            
            # 判断是否是文件链接(通过后缀名)
            # 这是一个简单的正则,匹配常见文档格式
            if re.search(r'\.(xls|xlsx|doc|docx|pdf|zip|rar)$', href, re.IGNORECASE):
                abs_url = self.fetcher.make_absolute_url(detail_url, href)
                file_type = abs_url.split('.')[-1]
                
                attachments.append({
                    "file_name": text or "Download", # 有时候 a 标签里没字
                    "file_url": abs_url,
                    "file_type": file_type
                })
                
        return attachments

8️⃣ 数据整合与导出(Controller)

将列表页抓取和详情页挖掘结合起来。

python 复制代码
import pandas as pd

class StatsController:
    def __init__(self):
        self.spider = StatsSpider("http://example.gov.cn/data/list.html")
        self.results = []

    def run(self, start_page=1, end_page=3):
        # 假设分页规律是 list_1.html, list_2.html... 或者 ?page=1
        # 这里演示 ?page=X 的情况
        for page in range(start_page, end_page + 1):
            logger.info(f"📄 Processing List Page {page}...")
            
            # 构造分页 URL
            if page == 1:
                target_url = self.spider.base_list_url
            else:
                # 这是一个伪代码逻辑,具体看网站
                target_url = f"{self.spider.base_list_url}?page={page}"
            
            html, real_url = self.spider.fetcher.fetch(target_url)
            if not html: continue
            
            # 1. 拿到列表项
            items = self.spider.parse_list_page(html, real_url)
            
            for item in items:
                logger.info(f"  🔍 Checking detail: {item['title'][:20]}...")
                
                # 2. 进入详情页
                detail_html, _ = self.spider.fetcher.fetch(item['detail_url'])
                if not detail_html: continue
                
                # 3. 提取附件
                attachments = self.spider.parse_detail_page_for_attachments(detail_html, item['detail_url'])
                
                if attachments:
                    for att in attachments:
                        # 只有找到了附件,才记录下来
                        self.results.append({
                            "indicator_name": item['title'],
                            "publish_date": item['date'],
                            "attachment_name": att['file_name'],
                            "download_link": att['file_url'],
                            "file_type": att['file_type'],
                            "source_url": item['detail_url']
                        })
                        logger.success(f"    📎 Found File: {att['file_name']} ({att['file_type']})")
                else:
                    logger.debug("    ❌ No attachment found.")
                
                # 详情页之间休眠,不要太快
                time.sleep(random.uniform(0.5, 1.5))
            
            # 列表页翻页休眠
            time.sleep(2)

    def save(self):
        df = pd.DataFrame(self.results)
        df.to_csv("output/stats_index.csv", index=False, encoding='utf-8-sig')
        logger.success(f"🎉 Job Done! Saved {len(df)} file records.")

9️⃣ 运行方式与结果展示(必写)

运行入口

python 复制代码
if __name__ == "__main__":
    bot = StatsController()
    bot.run(start_page=1, end_page=2) # 先试跑 2 页
    bot.save()

生成的 CSV 结果

indicator_name publish_date attachment_name download_link file_type source_url
2023年某市统计公报 2023-03-15 公报全文.pdf http://.../202303/P023.pdf pdf http://.../art/123.html
7月工业经济运行情况 2023-08-20 附表1:主要工业产品产量.xls http://.../data/tab1.xls xls http://.../art/456.html
7月工业经济运行情况 2023-08-20 附表2:分行业增加值.xls http://.../data/tab2.xls xls http://.../art/456.html

注意:你会发现同一篇详情页可能对应多行数据(因为包含多个附件),这正是我们想要的"原子化"索引。

🔟 常见问题与排错(Troubleshooting)

  1. **链接提取出来是 `javascript:openFile(123):

    • 现象:很多老政府网站用 JS 脚本来触发下载,href 并不是真实链接。
    • 对策 :这是最麻烦的情况。你需要分析那个 openFile 函数(通常在页面的

1️⃣1️⃣ 进阶优化(Optional)

  1. 文件下载器(Downloader)

    • 虽然我们只做索引,但可以写一个辅助脚本:读取生成的 CSV,过滤出 file_type= 'xls' 的行,然后批量下载。
    • df[df['file_type'].isin'xls', 'xlsx'])].apply(download_func, axis=1)
  2. **增量监控:

    • 记录上一次爬取的最新 publish_date。下次运行时,一旦遇到日期早于记录日期的,直接 break 退出循环。
  3. PDF 文本解析

    • 如果附件是 PDF,可以使用 pdfplumber 库,尝试把 PDF 里的表格硬抽取出来转为 Excel。这是一个巨大的坑,但也是巨大的机会。

1️⃣2️⃣ 总结与延伸阅读

复盘总结

我们完成了一个政务数据挖掘机

  • 我们克服了老旧网站的 SSL 和 编码问题。
  • 我们实现了跨页面的数据关联(列表页的时间 + 详情页的附件)。
  • 我们构建了一个高价值的数据索引

延伸阅读

  • Wget :如果你只是想简单粗暴地把整个网站所有 PDF 扒下来,Linux 的 wget -r -A.pdf http://target.com 可能比 Python 更快。
  • Gerapy / Scrapy:如果你要同时爬取全国 300 个地级市的统计局,请务必使用 Scrapy + Redis 分布式架构。

🌟 文末

好啦~以上就是本期的全部内容啦!如果你在实践过程中遇到任何疑问,欢迎在评论区留言交流,我看到都会尽量回复~咱们下期见!

小伙伴们在批阅的过程中,如果觉得文章不错,欢迎点赞、收藏、关注哦~
三连就是对我写作道路上最好的鼓励与支持! ❤️🔥

✅ 专栏持续更新中|建议收藏 + 订阅

墙裂推荐订阅专栏 👉 《Python爬虫实战》,本专栏秉承着以"入门 → 进阶 → 工程化 → 项目落地"的路线持续更新,争取让每一期内容都做到:

✅ 讲得清楚(原理)|✅ 跑得起来(代码)|✅ 用得上(场景)|✅ 扛得住(工程化)

📣 想系统提升的小伙伴 :强烈建议先订阅专栏 《Python爬虫实战》,再按目录大纲顺序学习,效率十倍上升~

✅ 互动征集

想让我把【某站点/某反爬/某验证码/某分布式方案】等写成某期实战?

评论区留言告诉我你的需求,我会优先安排实现(更新)哒~


⭐️ 若喜欢我,就请关注我叭~(更新不迷路)

⭐️ 若对你有用,就请点赞支持一下叭~(给我一点点动力)

⭐️ 若有疑问,就请评论留言告诉我叭~(我会补坑 & 更新迭代)


✅ 免责声明

本文爬虫思路、相关技术和代码仅用于学习参考,对阅读本文后的进行爬虫行为的用户本作者不承担任何法律责任。

使用或者参考本项目即表示您已阅读并同意以下条款:

  • 合法使用: 不得将本项目用于任何违法、违规或侵犯他人权益的行为,包括但不限于网络攻击、诈骗、绕过身份验证、未经授权的数据抓取等。
  • 风险自负: 任何因使用本项目而产生的法律责任、技术风险或经济损失,由使用者自行承担,项目作者不承担任何形式的责任。
  • 禁止滥用: 不得将本项目用于违法牟利、黑产活动或其他不当商业用途。
  • 使用或者参考本项目即视为同意上述条款,即 "谁使用,谁负责" 。如不同意,请立即停止使用并删除本项目。!!!
相关推荐
天天爱吃肉82182 小时前
跟着创意天才周杰伦学新能源汽车研发测试!3年从工程师到领域专家的成长秘籍!
数据库·python·算法·分类·汽车
m0_715575342 小时前
使用PyTorch构建你的第一个神经网络
jvm·数据库·python
甄心爱学习2 小时前
【leetcode】判断平衡二叉树
python·算法·leetcode
深蓝电商API2 小时前
滑块验证码破解思路与常见绕过方法
爬虫·python
Ulyanov3 小时前
Pymunk物理引擎深度解析:从入门到实战的2D物理模拟全攻略
python·游戏开发·pygame·物理引擎·pymunk
sensen_kiss3 小时前
INT303 Coursework1 爬取影视网站数据(如何爬虫网站数据)
爬虫·python·学习
玄同7653 小时前
我的 Trae Skill 实践|使用 UV 工具一键搭建 Python 项目开发环境
开发语言·人工智能·python·langchain·uv·trae·vibe coding
Yorlen_Zhang3 小时前
Python Tkinter Text 控件完全指南:从基础编辑器到富文本应用
开发语言·python·c#
HAPPY酷4 小时前
C++ 和 Python 的“容器”对决:从万金油到核武器
开发语言·c++·python