1. 项目背景与价值解析
公益项目数据抓取这个需求源于我去年参与的一次志愿者活动。当时团队需要分析近三年国内主流平台的公益项目发展趋势,但发现腾讯公益这类平台虽然提供了网页展示,却没有开放完整的历史数据接口。手动记录上百个项目信息不仅耗时耗力,而且难以保证数据准确性。
这个Python爬虫项目正是为了解决这类问题而生。通过自动化抓取腾讯公益平台上的热门项目数据,我们能够获得包括项目名称、筹款目标、已筹金额、参与人数、发起机构等关键字段。这些结构化数据至少能带来三个层面的价值:
- 公益研究支持:学者或调研机构可以分析公众偏好的公益类型(教育助学、疾病救助、扶贫等)、不同季节的捐赠热度变化等趋势
- 项目优化参考:公益组织能对比同类项目的展示方式、筹款话术,优化自身项目设计
- 透明化监督:定期抓取数据可建立公益项目执行进度的历史档案,促进公益行业的透明度
重要提示:爬虫开发必须遵守Robots协议,腾讯公益的robots.txt文件明确允许对/public开头的路径进行爬取,这正是我们目标数据所在的目录。同时需要控制请求频率,建议设置为每3秒一次请求,避免对服务器造成压力。
2. 技术方案设计
2.1 目标数据定位
首先需要明确我们要获取哪些具体数据。通过分析腾讯公益官网,一个典型的公益项目卡片包含以下关键信息字段:
| 字段名 | HTML定位方式 | 数据用途 |
|---|---|---|
| 项目标题 | div.title > a | 项目分类与关键词分析 |
| 筹款进度 | div.progress > span | 计算完成率与热门程度 |
| 目标金额 | div.target > em | 项目规模评估 |
| 已筹金额 | div.raised > em | 捐赠效果分析 |
| 捐赠人数 | div.donors > span | 公众参与度统计 |
| 项目封面图 | div.cover > img[src] | 宣传素材分析 |
| 项目链接 | a[href] | 后续深度爬取入口 |
2.2 技术栈选型
基于目标网站的现代前端架构(动态渲染、AJAX加载),我们采用以下技术组合:
- 请求库:requests + requests_html
- 普通页面用requests足够,但腾讯公益的部分数据通过滚动加载,需要requests_html处理动态内容
- 解析工具:pyquery + parsel
- pyquery的jQuery风格选择器更符合前端开发习惯
- parsel支持XPath和CSS双模式,应对复杂嵌套结构
- 存储方案:MongoDB
- 公益项目数据存在字段变化可能(如新增"项目进展"板块)
- 文档型数据库的schemaless特性更适配这种需求
# 典型依赖配置 requirements = [ 'requests>=2.26.0', 'requests-html>=0.10.0', 'pyquery>=1.4.3', 'pymongo>=4.0.1', 'fake-useragent>=0.1.11' ]2.3 反爬应对策略
腾讯公益虽然没有严格的反爬机制,但仍需注意:
- 请求头伪装:轮换User-Agent,添加合理的Referer
headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36...', 'Referer': 'https://gongyi.qq.com/' } - IP轮换:使用免费代理IP池(如github上的proxy-list)
proxies = { 'http': 'http://110.243.5.102:9999', 'https': 'https://110.243.5.102:9999' } - 请求间隔:随机延时2-5秒,避免触发频率限制
time.sleep(random.uniform(2, 5))
3. 核心代码实现
3.1 页面抓取模块
腾讯公益的热门项目采用分页加载,通过分析发现其API接口规律:
https://gongyi.qq.com/succor/list.htm?page=2实现分页抓取的要点:
def fetch_page(page_num): url = f'https://gongyi.qq.com/succor/list.htm?page={page_num}' try: session = HTMLSession() response = session.get(url, headers=headers, timeout=10) response.raise_for_status() # 渲染JavaScript执行后的内容 response.html.render(sleep=2) return response.html.html except Exception as e: print(f"Page {page_num} fetch failed: {str(e)}") return None实测发现:直接请求HTML页面比调用内部API更稳定,虽然需要渲染JS会增加耗时,但避免了接口变动的维护成本。
3.2 数据解析模块
使用PyQuery提取关键数据的典型实现:
from pyquery import PyQuery as pq def parse_project(html): doc = pq(html) projects = [] for item in doc('.project-list .project-item').items(): project = { 'title': item.find('.title').text(), 'target': float(item.find('.target em').text()[1:].replace(',', '')), 'raised': float(item.find('.raised em').text()[1:].replace(',', '')), 'progress': item.find('.progress span').attr('style'), 'donors': int(item.find('.donors span').text()), 'cover': item.find('.cover img').attr('src'), 'url': 'https://gongyi.qq.com' + item.find('a').attr('href'), 'crawl_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S') } # 计算完成百分比 if project['target'] > 0: project['completion'] = round( project['raised'] / project['target'] * 100, 2 ) projects.append(project) return projects3.3 数据存储模块
MongoDB存储的优化实践:
from pymongo import MongoClient from pymongo.errors import DuplicateKeyError class MongoDBPipeline: def __init__(self, mongo_uri, mongo_db): self.mongo_uri = mongo_uri self.mongo_db = mongo_db @classmethod def from_crawler(cls, crawler): return cls( mongo_uri='mongodb://localhost:27017', mongo_db='tencent_public_welfare' ) def process_item(self, item, spider): try: # 根据项目URL创建唯一索引避免重复 self.db['projects'].update_one( {'url': item['url']}, {'$set': dict(item)}, upsert=True ) except DuplicateKeyError: pass return item4. 实战技巧与避坑指南
4.1 动态内容处理经验
腾讯公益在2023年改版后,部分数据改为滚动加载。我们通过Chrome开发者工具分析发现:
- 首屏数据在初始HTML中
- 后续数据通过
/api/project/list接口加载 - 接口需要携带
x-requested-with: XMLHttpRequest头
解决方案:
async def fetch_ajax(page): ajax_url = 'https://gongyi.qq.com/api/project/list' params = { 'page': page, 'size': 20, 'type': 'recommend' } async with aiohttp.ClientSession() as session: async with session.get(ajax_url, params=params, headers={'x-requested-with': 'XMLHttpRequest'}) as resp: return await resp.json()4.2 数据清洗要点
原始数据需要特别注意:
- 金额字段包含"¥"符号和逗号分隔(如"¥1,234.56")
- 进度条是CSS宽度(如"width: 78.23%;")
- 部分项目可能有"已结项"特殊标记
清洗函数示例:
def clean_data(project): # 处理金额 project['raised'] = float( project['raised'].replace('¥', '').replace(',', '') ) # 提取进度百分比 if 'progress' in project: match = re.search(r'width:\s*([\d.]+)%', project['progress']) if match: project['progress_pct'] = float(match.group(1)) # 标记特殊状态 project['is_ended'] = '已结项' in project.get('status', '') return project4.3 定时任务优化
建议使用APScheduler实现定时抓取:
from apscheduler.schedulers.blocking import BlockingScheduler scheduler = BlockingScheduler() @scheduler.scheduled_job('cron', hour=3, minute=30) def daily_crawl(): for page in range(1, 6): # 抓取前5页 html = fetch_page(page) projects = parse_project(html) save_to_mongo(projects) scheduler.start()5. 数据分析应用示例
获取数据只是第一步,如何利用这些数据创造价值才是关键。以下是两个实际应用场景:
5.1 公益类型热度分析
import pandas as pd from collections import Counter # 从MongoDB加载数据 projects = list(db.projects.find()) # 提取标题关键词 keywords = [] for p in projects: if '助学' in p['title']: keywords.append('教育助学') elif '医疗' in p['title'] or '疾病' in p['title']: keywords.append('医疗救助') elif '乡村' in p['title'] or '扶贫' in p['title']: keywords.append('扶贫发展') # 统计分布 pd.DataFrame.from_dict( Counter(keywords), orient='index', columns=['count'] ).sort_values('count').plot.barh()5.2 捐赠效果影响因素分析
使用Scikit-learn分析哪些因素影响筹款完成率:
from sklearn.ensemble import RandomForestRegressor # 准备特征矩阵 X = pd.DataFrame([{ 'target_amount': p['target'], 'donors': p['donors'], 'has_cover': int(bool(p['cover'])), 'title_len': len(p['title']) } for p in projects]) # 目标变量 y = [p['completion'] for p in projects] # 训练模型 model = RandomForestRegressor() model.fit(X, y) # 特征重要性 pd.Series(model.feature_importances_, index=X.columns).sort_values()6. 法律合规与道德考量
虽然技术上是可行的,但爬取公益数据需要特别注意:
- 数据使用范围:仅用于分析研究,不得用于商业用途
- 隐私保护:绝对不抓取捐赠人个人信息
- 数据公开:如果分享分析结果,需去除敏感字段
- 服务器负载:严格遵守爬虫间隔时间,避免影响正常用户访问
建议在代码开头添加如下声明:
""" 腾讯公益数据爬虫 - 使用声明 1. 本工具仅用于技术学习与研究目的 2. 数据抓取频率控制在每分钟不超过20次请求 3. 不存储、不传播任何捐赠人个人信息 4. 分析结果如公开需注明数据来源 """这个项目让我深刻体会到,技术可以成为公益事业的加速器。通过合理使用爬虫技术,我们能够帮助公益组织更高效地获取行业洞察,最终让每一份爱心都能更精准地传递到需要的地方。后续可以考虑增加自动生成公益项目分析报告的功能,用数据可视化让公益效果更透明可见。