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爬虫实战》,再按目录大纲顺序学习,效率十倍上升~

✅ 互动征集

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

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


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

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

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


✅ 免责声明

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

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

  • 合法使用: 不得将本项目用于任何违法、违规或侵犯他人权益的行为,包括但不限于网络攻击、诈骗、绕过身份验证、未经授权的数据抓取等。
  • 风险自负: 任何因使用本项目而产生的法律责任、技术风险或经济损失,由使用者自行承担,项目作者不承担任何形式的责任。
  • 禁止滥用: 不得将本项目用于违法牟利、黑产活动或其他不当商业用途。
  • 使用或者参考本项目即视为同意上述条款,即 "谁使用,谁负责" 。如不同意,请立即停止使用并删除本项目。!!!
相关推荐
老白干16 分钟前
jjwt 0.9.1 在 JDK 11+ 上的两个“坑”与完整解决方案
java·python·log4j
笨鸟先飞,勤能补拙7 小时前
AI 赋能网络安全:技术全景、成熟度评估与实战案例
人工智能·python·安全·web安全·网络安全·sqlite·github
长和信泰光伏储能8 小时前
京津冀光伏发电:绿色能源的未来之路
python·能源
浦信仿真大讲堂8 小时前
从重复操作到自动化闭环:如何让 CST 与 Python 真正协同起来
python·自动化·cst·仿真软件·达索软件
Gu Gu Study8 小时前
ScoutLoop开放域深度研究引擎(agent的初步设计想法)
人工智能·python
卷无止境9 小时前
写代码这件事,到底该讲究点什么?
后端·python
卷无止境9 小时前
循环复杂度到底在算什么,Python 代码怎么才能写得让人一看就懂
后端·python
lpfasd1239 小时前
MediaCrawler 项目深度分析
chrome·python·chrome devtools
Dxy123931021610 小时前
Python项目打包成EXE完整教程(PyInstaller实战避坑)
开发语言·python
bamb0010 小时前
一个项目带你入门AI应用开发01
python