我最早接触Python爬虫是因为一个很现实的需求想搞清楚腾讯公益平台上那些热门公益项目到底是怎么筹款的、项目进展更新得怎么样。当时身边有朋友参与过线上捐赠却说不清自己捐的钱最后用在了哪些环节这让我意识到公益数据不该只是平台后台的一串数字它应该被看见、被监督、被传播。于是就有了这个项目——用Python爬虫去抓取腾讯热门公益项目数据把散落在网页里的信息整理成结构化表格再做成动态报表。整个过程没有特别高深的技巧用的都是requests、BeautifulSoup这类基础库但胜在完整从分析网页结构、模拟请求到数据清洗、存储再到定时更新和可视化一条龙跑通。如果你也想练手爬虫又不想抓那些没什么意义的示例网站这个选题既有人情味又有真实的业务场景非常适合拿来当实战项目。1. 项目背景与目标拆解1.1 为什么选择腾讯公益平台作为爬取对象选择腾讯公益首先是因为它的公开数据量足够大、项目维度丰富涵盖了教育助学、医疗救助、环境保护、乡村振兴等多个类别。对于爬虫项目来说数据源越复杂、字段越多样练手价值就越高。其次腾讯公益的页面结构相对规范虽然有一些动态加载和反爬机制但难度适中正好适合从零开始学习爬虫的人挑战。更重要的一点公益项目的数据天然自带“可验证”属性。一个项目募集了多少钱、还有多少缺口、执行进度到哪一步、善款用途怎么写这些信息如果能被批量抓取并保存下来我们就能自己做一个“公益项目监控表”定期追踪项目变化甚至还能发现某些项目更新停滞、文案长期不修改等异常现象。这种应用场景很打动我——代码不是冷冰冰的工具用它来传递善意、监督善行本身就是一件很有意义的事。1.2 项目要解决的核心问题这个项目表面上只是“抓数据”但实际操作时面临四个核心问题数据从哪来腾讯公益的列表页和详情页哪些信息是静态HTML里直接有的哪些是异步接口返回的必须先摸清页面结构才能决定用requests直接拿还是用selenium模拟浏览器。请求怎么发接口需要哪些参数签名、时间戳、offset翻页逻辑是什么如果直接复制浏览器的请求头能不能稳定获取数据数据怎么存爬下来的是JSON还是HTML字段嵌套深度怎么处理是存CSV方便查看还是存SQLite方便后续查询更新怎么做公益项目是动态变化的怎么让脚本每天自动跑一遍把新增数据和变化数据记录下来而不是重复全量抓取这些问题没有标准答案必须结合腾讯公益的实际页面来调整。我翻遍了控制台看了几十次请求日志才慢慢摸清规律。整个过程就是典型的“爬虫实战”路径分析、猜测、验证、修正循环往复。1.3 技术选型与开发环境我的技术方案很朴素Python 3.9语言基础安装方法不再赘述重点是用venv建一个干净环境避免依赖冲突。requests发HTTP请求处理接口调用。BeautifulSoup4 lxml解析HTML页面提取静态字段。jsonpath从嵌套JSON里提取特定字段比反复写for循环省事很多。pandas数据清洗、去重、转换导出Excel。SQLite3本地存储Python内置零配置。matplotlib pyecharts生成图表直观展示项目募集进度、分类占比。这里想多说一句很多人一上来就让我推荐“最好用的爬虫框架”我一般劝他们先别急着上Scrapy。爬虫的学习路径应该是“requests——Scrapy——更高阶的异步框架”。先用requests把页面分析、请求构造、数据提取这套基本功练扎实再上框架你会理解框架的每个组件为什么存在。这个项目用requests完全够了。2. 爬虫核心设计与数据解析2.1 页面结构分析与接口定位打开腾讯公益的“项目列表”页面第一眼看到的是一个个公益项目卡片上面有项目名称、所属机构、募捐金额、已获捐次数等。很多新手习惯直接用BeautifulSoup去解析这些卡片但我的经验是先去开发者工具里看网络请求找到真正的数据源头。我按F12切到Network面板刷新页面很快发现几个XHR请求。逐个点开看响应体其中有一个接口的返回是标准的JSON字段里包含了项目编号、名称、募捐金额、筹款目标、创建时间等十多个有用的属性。这个接口就是我们要抓的主目标。记住一个原则页面是给人看的接口才是给程序用的。如果能找到稳定的JSON接口就不要去解析HTML因为HTML结构稍微改个CSS类名你的代码就废了而JSON字段通常会更稳定。我在这个项目里约80%的数据来自接口剩下的项目详情文案、图片地址等信息再通过详情页HTML补充拼接。2.2 请求构造与参数计算找到接口后重点看它的请求参数和请求头。腾讯公益的这个列表接口有几个关键参数query搜索关键词留空就是全量列表。sort排序方式我们选的是“热门”对应一个固定值。offset起始偏移量类似数据库的limit offset。pageSize每页条数一般固定为20。cid项目分类ID比如助学、医疗等不传就是全部。gongyi_id类似channel ID用于统计来源。构造请求时我写了这样一段代码import requests def build_params(page, page_size20): params { query: , sort: hot, offset: (page - 1) * page_size, pageSize: page_size, cid: , gongyi_id: web_page, from: web, } return params session requests.Session() session.headers.update({ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Referer: https://gongyi.qq.com/, Accept: application/json, text/plain, */*, }) url https://gongyi.qq.com/xxx/list # 实际接口地址请以你抓包为准 resp session.get(url, paramsbuild_params(1), timeout10) print(resp.status_code, resp.json().get(data, {}).get(list, [])[0])这里有两个小细节特别值得注意。第一个是Session的使用——requests.Session会自动保存Cookie并复用TCP连接对于需要连续翻页的爬虫来说比每次创建新的requests.get更稳定、更快。第二个是Referer头和User-Agent——很多接口会校验这两个字段哪怕只是按浏览器的默认值填上也能大幅度降低被拒绝的概率。实际请求时如果返回的状态码是200但数据为空不要急着怀疑代码。先检查resp.text里的内容如果出现类似“验证码”或“risk”的字样说明触发了风控。我的解决方案是放慢请求频率每页之间sleep 0.5到1秒并且模拟真实的浏览器行为比如先请求一次首页再请求接口。2.3 数据清洗与字段提取接口返回的JSON里字段名是英文或拼音缩写直接存起来不友好。我做了下面的映射表统一转换成中文列名方便后续分析和制表接口原始字段转换后的列名说明project_id项目编号唯一标识用于关联详情project_name项目名称公益项目标题org_name所属机构发起募捐的组织名称raise_amount已筹金额单位通常为元target_amount目标金额项目设定的筹款目标raise_percent完成进度已筹/目标接口可能已算好raise_count捐赠次数总参与人次create_time创建时间项目发起时间update_time更新时间最近信息更新时间拿到原始JSON后我一般先转成DataFrame再统一做类型转换和缺失值处理。金额字段可能是字符串也可能带千分位逗号需要用pandas的to_numeric配合errorscoerce强制转换。时间字段可能是时间戳也可能是格式化字符串需要统一成yyyy-MM-dd HH:mm:ss格式。下面这段是我常用的数据清洗流程import json import pandas as pd def parse_project_list(raw_json): data json.loads(raw_json) items data[data][list] rows [] for item in items: rows.append({ 项目编号: item.get(project_id), 项目名称: item.get(project_name), 所属机构: item.get(org_name), 已筹金额: float(item.get(raise_amount, 0)), 目标金额: float(item.get(target_amount, 0)), 完成进度: round(float(item.get(raise_percent, 0)) * 100, 2), 捐赠次数: int(item.get(raise_count, 0)), 创建时间: pd.to_datetime(item.get(create_time)), 更新时间: pd.to_datetime(item.get(update_time)), }) df pd.DataFrame(rows) df[缺口金额] df[目标金额] - df[已筹金额] df[缺口比例] (df[缺口金额] / df[目标金额] * 100).round(2) return df把“缺口金额”和“缺口比例”也一并算出来这个设计是我自己加的。因为当时我非常想知道哪些热门项目看起来火爆但其实离目标还很远。这个字段后来真的排上大用场——我筛选出了几个筹款进度低于20%但浏览次数很高的项目发现它们的文案没有及时更新这其实是一个值得公益机构反思的细节。3. 数据存储与可视化展示3.1 存储方案选择与建表思路爬下来的数据量不大一天全量也就几千条用SQLite绰绰有余。SQLite是Python自带的一个轻量级数据库不需要单独启动服务最适合这种小型爬虫项目。不过我没有简单地把每日数据覆盖写入而是设计了一个带日期的累计表这样能追踪数据变化。建表语句基本长这样CREATE TABLE IF NOT EXISTS project_data ( crawl_date TEXT, project_id TEXT, project_name TEXT, org_name TEXT, raise_amount REAL, target_amount REAL, raise_percent REAL, raise_count INTEGER, create_time TEXT, update_time TEXT, PRIMARY KEY (crawl_date, project_id) );把crawl_date和project_id设成联合主键好处是同一项目在同一天不会重复插入。如果你一天跑多次脚本可以使用INSERT OR REPLACE INTO来更新简单粗暴也不会产生冗余数据。这里我要特别提醒一下不要直接把爬取的数据一股脑存成CSV。CSV适合一次性浏览但当你想知道“某个项目上周的已筹金额是多少”时CSV几乎没法方便查询。而用SQLite一条SQL就能搞定SELECT project_name, raise_amount, update_time FROM project_data WHERE project_id 某项目ID ORDER BY crawl_date;这种历史追踪能力让我能画出每个项目的筹款趋势曲线直观看到公益项目的“长跑能力”。3.2 可视化分析与图表输出数据存起来以后就要让数字说人话。我做了三种图表每种回答一个具体问题分类占比饼图当前腾讯公益的热门项目中医疗救助、教育助学、灾害救援、环境保护各自占多少筹款进度条形图选前20个最热门的项目比较它们的完成进度哪些项目“叫好不叫座”一目了然。金额分布箱线图所有热门项目的已筹金额分布如何有没有极端异常值用pyecharts画图非常直观下面是一个简化示例from pyecharts.charts import Bar from pyecharts import options as opts def draw_progress_top20(df): df_top df.nlargest(20, 捐赠次数)[[项目名称, 完成进度]] bar ( Bar() .add_xaxis(df_top[项目名称].tolist()) .add_yaxis(完成进度(%), df_top[完成进度].tolist()) .set_global_opts( title_optsopts.TitleOpts(title腾讯热门公益项目进度TOP20), yaxis_optsopts.AxisOpts(name完成进度(%)), xaxis_optsopts.AxisOpts(name项目名称, axislabel_optsopts.LabelOpts(rotate30)) ) ) return bar.render(progress_top20.html)图表生成后我还会把数据导出成一份Excel发到团队群里。pandas里一句df.to_excel(腾讯公益数据快照.xlsx)就搞定了不过要记得先安装openpyxl库否则会报错。3.3 自动化定时运行公益数据每天都在变化我不可能天天手动跑脚本。所以我把爬虫写成了一个可以定时执行的任务。在Windows环境下用任务计划程序在Linux/Mac下用crontab都能实现每天定时运行。我的脚本入口是这样组织的# main.py import time from datetime import datetime from crawler import fetch_all_pages from parser import parse_project_list from storage import save_to_sqlite def main(): today datetime.now().strftime(%Y-%m-%d) raw_data fetch_all_pages(max_page20) df parse_project_list(raw_data) save_to_sqlite(df, crawl_datetoday) print(f{today}: 抓取完成共 {len(df)} 条数据) if __name__ __main__: main()crontab的写法也很简单比如每天早上8点跑一次0 8 * * * cd /path/to/project /usr/bin/python3 main.py run.log 21这里需要注意一个坑crontab里的Python必须是绝对路径因为cron环境变量里可能没有Python的PATH。别问我为什么知道的我第一次配定时任务就栽在这上面日志里全是command not found。4. 常见问题与实战避坑4.1 请求被拦截怎么办爬虫跑着跑着突然拿不到数据大概率是被风控了。腾讯公益的接口对频率有一定限制单次抓取几百条问题不大但如果你用多线程疯狂并发很快IP就会被临时限制。我总结的应对策略是按优先级排列降低频率加time.sleep(0.5)把每个请求间隔控制在0.5秒以上。伪装请求头随机换User-Agent不要一直用同一个Chrome版本号。使用Session保持连接让服务端认定你是同一个“用户”避免每次请求都像新客。增加重试机制遇到429或5xx错误时等待5秒后重试最多3次。下面是一个简单的重试装饰器非常实用import time import requests from functools import wraps def retry(times3, delay2): def decorator(func): wraps(func) def wrapper(*args, **kwargs): for i in range(times): try: return func(*args, **kwargs) except Exception as e: print(f第{i1}次请求失败: {e}) if i times - 1: raise time.sleep(delay) return wrapper return decorator retry(3, 2) def fetch_json(url, params): resp requests.get(url, paramsparams, timeout10) resp.raise_for_status() return resp.json()这个装饰器可以给任何请求函数加上重试能力是我的爬虫工具箱里最常用的东西。4.2 动态数据加载的应对策略腾讯公益的列表页数据确实是通过接口加载的但如果你遇到某些数据是接口拿不到的比如项目详情页里的图文介绍、执行报告这些往往还是HTML渲染的。这时BeautifulSoup就派上用场了。详情页HTML里项目介绍可能被藏在多个div嵌套中甚至有些文案是先截断后加载的。我的做法是先用BeautifulSoup定位到正文容器from bs4 import BeautifulSoup def fetch_project_detail(project_id): detail_url fhttps://gongyi.qq.com/xxx/detail?projectId{project_id} resp requests.get(detail_url, headerssession.headers, timeout10) soup BeautifulSoup(resp.text, lxml) desc_box soup.select_one(.project-description) if desc_box: return desc_box.get_text(stripTrue) return 这里的关键是用的lxml解析器比默认的html.parser更能容忍不规范的页面代码。另外get_text(stripTrue)能自动去除首尾空白和多余换行让提取出来的文案干净很多。在解析过程中我有一次遇到页面中嵌入的JSON数据里有转义的\u字符用json.loads总是报错。后来才反应过来需要先把HTML中quot;之类的实体转换回原始字符再用bytes(...).decode(unicode_escape)处理。这种坑不踩一次真的想不到。4.3 爬虫伦理与合规注意写爬虫的人必须时刻盯紧合规底线。我在项目一开始就给自己定了几条原则这里也分享给大家只爬公开数据不登录、不绕过权限控制腾讯公益页面上能直接看到的数据才抓需要用户登录才能看的通通不碰。控制请求频率把服务端当成公共资源别人也要用不能因为你图快就把别人的路堵了。标注数据来源如果后续发布分析报告一定要注明数据来源于腾讯公益平台不冒充原创数据。尊重robots协议虽然很多公益平台没有严格限制但养成先查看robots.txt的习惯没有坏处。不涉及个人隐私公益项目数据里可能包含受助人的信息我在爬取和展示时只保留机构名和项目名绝不抓取具体受益人信息。有一次我在做数据去重的时候发现某个项目的详情页里竟然包含了发起人的个人联系方式。这无疑超出了抓取范围我立刻在解析代码里加了过滤规则并删除了已抓到的相关字段。做技术的人应该有自己的底线这个底线就是我们造工具的初衷是让世界更透明而不是让任何个体被暴露在风险中。5. 运行效果与项目收获5.1 成功抓取的数据概览这个爬虫跑了一周后我汇总了一下成果累计抓取了腾讯公益平台2000多个热门项目的数据字段包括项目名称、所属机构、已筹金额、目标金额、完成进度、捐赠次数、创建时间和更新时间等。经过清洗后有效数据约1950条覆盖教育、医疗、救灾、环保、社区发展等主流公益类别。我特别关注了“完成进度”这个指标发现几类很有意思的现象医疗救助类的项目进度普遍偏高部分项目甚至在一天内爆满说明这种需求非常急迫网友的响应速度也最快。环境保护类的项目数量多但单个项目的筹款进度相对缓慢平均完成进度只有30%左右。有个别项目更新停滞最近更新时间停留在几个月前但筹款页面仍然开放这种情况值得捐方警惕。这些结论如果只靠人工一条条翻页面可能看十几次都发现不了规律。现在用Python爬虫自动抓取、自动统计不仅节省了时间还让我第一次体会到“用数据理解公益”的感受。5.2 个人技能精进与心得这个项目对我的技术提升是全方位的。以前我写爬虫抓完列表页就当完事了这次为了把数据做厚我接触了jsonpath、pandas、SQLite和pyecharts。每一个工具都不难但当它们组合在一起时能产生非常大的能量。更重要的是我学会了从一个“项目管理者”的角度看待爬虫。单纯把数据抓下来没有任何价值只有围绕数据做加工、存储、分析、可视化形成完整闭环才算真正解决问题。这也是为什么我不建议初学者只写一个“抓取豆瓣top250”的小脚本就停手——试试再往前走一步把抓来的数据存起来定期再抓一次对比变化分析规律你会学到更多。5.3 后续扩展与优化方向这个项目目前还只是“单机版”。我计划下一步做三件事增量爬取每次只抓取项目详情页的update_time和raise_amount比较与上次的差异只有变化时才更新数据库降低请求量。异常监测写一个告警脚本当某个项目的update_time超过30天未更新时自动发邮件提醒关注。数据报表网页化用Flask写一个简单页面通过数据库读取历史数据展示项目排行榜、趋势图甚至可以做一个“爱心地图”。另外如果你跑这个项目遇到困难我建议先不要急着问别人自己动手抓包是最快的成长方式。浏览器开发者工具的Network面板是你最好的老师。多看几个接口多对比参数你会慢慢发现爬虫的技术壁垒并不高真正的壁垒在于你对目标平台规则的尊重程度和异常分析能力。我到现在还记得第一次跑通整个流程的那个晚上看到终端里一行一行滚过项目名称和金额数字紧接着Excel里生成了整整齐齐的表格那种“代码真的能把信息组织起来”的成就感比打游戏通关还过瘾。后来我又用同样的方法去抓了其他公益公开数据做了个简单的“公益透明榜”虽然只是个人玩具但真的有朋友看到后去确认了几个曾捐助过的项目甚至发现其中一项确实已经很久没有更新进展了。这让我更坚定地相信爬虫只是一个技术手段重点在于你想用这段代码做什么。抓取腾讯热门公益项目的数据看起来是在练习Python、练习requests、练习数据清洗但本质上是在练习我们如何更关心这个世界如何用自己的技能让善意落地得明明白白。如果你正准备学爬虫不妨也找一件你在意的事用代码把它抓下来、看清楚、传播出去。这才是这个项目最值得传递的价值。
阅读完成 · 觉得有帮助?