Python爬虫实战:混合策略高效抓取微博数据与反反爬设计
2026/9/2 9:44:40 网站建设 项目流程

简介:本资源是一款面向Python中级开发者与数据采集研究者的微博自动化抓取工具,聚焦SinaWeibo平台用户画像、社交关系链及超级话题生态的数据获取需求,有效解决公开数据受限、反爬机制复杂等实际采集难点。压缩包共41个文件,总大小10.99MB,包含12个核心Python源码(如weibo_cn_async.py、login.py、redis_cookies.py)、16个pyc字节码、1个YAML账号配置文件、1个DLL验证码识别库(yundamaAPI-x64.dll)、1个可执行exe、1个日志文件及SpiderFramework.jpg框架图等,覆盖登录鉴权、异步抓取、Redis缓存、验证码识别与话题深度遍历等关键模块。已有354人学习下载。用户可直接部署运行,快速获取指定用户的主页信息、关注/粉丝列表、超级话题关联用户及其互动数据,并通过setting.py与account.yaml灵活配置代理、并发数与存储策略;配套logging.conf与容器ID标识文件进一步提升调试效率与工程化能力。

1. 项目概述与核心价值

最近在整理过往项目时,翻出了一个尘封已久的代码仓库——“SinaWeiboSpider”。这是一个用Python写的微博数据采集工具,虽然现在微博的反爬策略已经升级了好几轮,但这个项目的设计思路和源码结构,对于想入门网络爬虫、理解如何处理复杂动态网站、乃至进行社交媒体数据分析的朋友来说,依然是一份非常扎实的“练手标本”。它不只是一个能跑起来的脚本,更体现了面对一个大型商业网站时,从请求模拟、数据解析到反反爬策略的完整工程化思考。今天,我就把这个项目的“箱底货”翻出来,结合最新的技术环境,重新拆解一遍它的设计源码,聊聊其中那些当时踩过的坑和现在依然通用的技巧。

简单说,这个爬虫的核心目标是:模拟真实用户行为,稳定、高效地从微博平台抓取指定的公开数据,比如用户主页信息、微博正文、评论、转发量等,并将这些非结构化的网页数据,清洗、整理成结构化的格式(如CSV或JSON)以供后续分析。它适合有一定Python基础,想从“写个小脚本抓静态页”进阶到“应对复杂JS渲染和风控”的开发者。通过剖析这个项目,你不仅能学会如何使用requestsselenium等库,更能理解如何设计一个健壮、可维护的爬虫系统架构。

2. 整体架构设计与技术选型解析

2.1 为什么选择混合请求策略?

早期的微博页面相对简单,大量数据直接嵌在HTML源码中。但随着前端技术发展,微博变成了一个重度依赖JavaScript渲染的动态单页应用(SPA)。这意味着,单纯用requests库获取HTML,很可能拿到的是一个没有数据的空壳页面,关键数据都通过后续的Ajax请求异步加载。

因此,这个爬虫没有采用单一技术,而是设计了一套“混合请求策略”:

  1. 主请求使用Requests + 自定义Session:对于登录态维持、部分接口API调用,requests库轻量、高效的优势明显。我们通过它来模拟登录、获取Cookies,并管理整个会话状态。
  2. 动态内容渲染依赖Selenium:对于必须执行JS才能生成内容的页面(如某些需要滚动加载的微博列表),我们启用selenium配合ChromeDriver。这里的关键不是全程用Selenium(那样太慢),而是“按需启动”,只在必要时用它来获取渲染后的页面源码或触发特定JS事件。
  3. 核心数据抓取瞄准API接口:通过浏览器开发者工具的“网络(Network)”面板监控,我们发现微博的大部分数据(如微博详情、评论列表)都有对应的JSON格式API接口。直接调用这些接口,效率远高于解析HTML。爬虫的核心工作之一,就是逆向工程这些接口的参数规律。

注意:直接爬取公开API接口虽然高效,但必须严格遵守robots.txt协议(尽管很多API接口不在其中明确禁止),并严格控制请求频率,避免对目标服务器造成压力。我们的设计原则是“获取公开数据,模拟人类行为”。

2.2 项目模块化分解

为了让代码清晰且易于维护,整个项目被分解为以下几个核心模块:

  • spider_core.py:爬虫引擎核心。负责调度整个抓取流程,管理请求队列,分配任务给下载器,并协调解析器和管道。
  • downloader.py:下载器。封装了requestsselenium两种下载方式。根据任务类型(如“获取API数据”或“渲染动态页”)自动选择合适的方法,并集成代理IP、请求头随机化、异常重试等逻辑。
  • parser.py:解析器。这是最需要“手艺”的部分。既包含用BeautifulSouplxml解析静态HTML的规则,也包含解析JSON接口数据的逻辑。针对微博多变的页面结构,这里设计了多套解析方案以应对不同页面模板。
  • storage_pipeline.py:数据存储管道。负责将解析后的结构化数据持久化。支持多种后端,如直接写入CSV文件、存入MySQL数据库,或者发送到消息队列(如Redis)供其他系统消费。采用了插件化设计,方便扩展。
  • utils/:工具函数集。包括日志记录、配置文件读取、时间处理、加密参数生成(针对某些需要sign参数的接口)、验证码识别(备用方案)等辅助功能。
  • config.py:配置文件。将用户账号(已废弃,见下文)、目标用户ID、抓取深度、请求间隔、存储路径等所有可配置项集中管理。

这种模块化设计的好处是“高内聚、低耦合”。比如,当微博更改了页面结构,你通常只需要修改parser.py中的相应解析函数;如果想更换存储方式,也只需改动storage_pipeline.py,其他模块几乎不受影响。

3. 核心技术与难点实战剖析

3.1 登录与会话维持的演变

最早的版本尝试了模拟登录。我们需要处理微博的复杂登录流程,包括预登录接口获取su(加密后的用户名)、servertimenonce等参数,然后进行RSA加密提交。这套流程不仅复杂,而且一旦微博的加密算法或流程变动,爬虫就会立即失效。

更稳健的方案是使用Cookie:我们放弃了对登录接口的硬编码模拟,转而采用更实用的方法——手动登录后获取Cookie。具体操作是:用浏览器正常登录微博,然后通过开发者工具复制出完整的Cookie字符串,将其配置到爬虫的config.py或会话管理中。这样爬虫就能以已登录状态发起请求,绕开了复杂的登录逻辑。当然,Cookie会过期,这就需要一套Cookie池管理和更新机制。

# 示例:在downloader中设置携带Cookie的会话 import requests from config import WEIBO_COOKIES session = requests.Session() # 将复制的Cookie字符串转换为字典格式设置到会话中 cookies_dict = {item.split('=')[0]: item.split('=')[1] for item in WEIBO_COOKIES.split('; ')} requests.utils.add_dict_to_cookiejar(session.cookies, cookies_dict) # 后续所有使用该session的请求都将携带登录态 response = session.get('https://weibo.com/ajax/profile/info?uid=123456789')

3.2 逆向工程API接口

这是爬虫高效与否的关键。以抓取用户微博列表为例:

  1. 打开目标用户主页,如https://weibo.com/u/123456789
  2. 按F12打开开发者工具,切换到“网络(Network)”面板,并筛选“XHR”或“Fetch”请求。
  3. 滚动页面触发微博加载,观察新出现的请求。你会发现一个类似https://weibo.com/ajax/statuses/mymblog?uid=...&page=...的请求,其响应正是结构清晰的JSON数据,包含了微博ID、正文、发布时间、转发评论点赞数等。

接下来是分析请求参数:

  • uid: 用户ID,固定。
  • page: 页码,用于分页。
  • feature: 这个参数经常变化,需要观察多页请求来确定其生成规律。有时它可能是一个固定值,有时可能与时间戳或页面特征相关。
  • _rnd: 或称为_t,通常是一个时间戳,用于防止缓存。

我们的爬虫需要做的就是,构建一个参数生成器,能够模拟出这些合法参数,然后循环请求不同页码即可。对于需要sign签名的接口,逆向难度更大,可能需要分析前端JS的加密代码。

3.3 动态渲染与Selenium的精准使用

当目标数据无法通过直接API获取,或者页面交互复杂时,才祭出Selenium。例如,抓取微博“转发详情”列表,早期版本没有独立API,需要点击“转发”按钮弹出模态框。

优化技巧:不是所有操作都需要可视化浏览器。

from selenium import webdriver from selenium.webdriver.chrome.options import Options # 配置无头模式,不显示浏览器窗口 chrome_options = Options() chrome_options.add_argument('--headless') chrome_options.add_argument('--disable-gpu') chrome_options.add_argument('--no-sandbox') # Linux服务器常用 driver = webdriver.Chrome(options=chrome_options) # 先访问页面,让JS执行 driver.get('https://weibo.com/123456789/xxxxxx') # 等待必要元素加载,而非固定sleep from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC try: # 等待“转发”按钮出现并点击 forward_button = WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.XPATH, "//a[contains(@action-type, 'fl_forward')]")) ) forward_button.click() # 等待转发列表弹出 forward_list = WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.CLASS_NAME, "list_box")) ) # 获取渲染后的HTML源码,交给解析器处理 page_source = driver.page_source finally: driver.quit() # 用完及时关闭,释放资源

关键点在于:使用WebDriverWait进行显式等待,而不是time.sleep,这样更高效稳定。并且,一旦获取到所需数据源的HTML或触发API请求后,就立即关闭浏览器实例,避免资源浪费。

3.4 数据解析与清洗

拿到数据(HTML或JSON)后,解析是关键一步。对于JSON接口,直接用json.loads()即可。对于HTML,我们使用lxml,因为它比BeautifulSoup解析速度更快。

from lxml import etree import json def parse_weibo_html(html_content): """解析单条微博HTML卡片,提取信息""" tree = etree.HTML(html_content) weibo_item = {} # 使用XPath定位元素,这里路径需要根据实际页面结构调整 # 示例:提取微博正文 content_node = tree.xpath('//div[@class="weibo-text"]/text()') weibo_item['content'] = content_node[0].strip() if content_node else '' # 提取发布时间 pub_time_node = tree.xpath('//a[@class="date"]/@title') weibo_item['pub_time'] = pub_time_node[0] if pub_time_node else '' # 提取互动数据(转发、评论、赞) # 这类数据可能在属性中,如 <a action-type="fl_forward">转发(100)</a> forward_node = tree.xpath('//a[@action-type="fl_forward"]/text()') if forward_node: # 使用正则表达式提取数字 import re weibo_item['reposts_count'] = int(re.search(r'\d+', forward_node[0]).group()) return weibo_item def parse_api_json(json_data): """解析API返回的JSON数据""" data = json.loads(json_data) weibo_list = data.get('data', {}).get('list', []) parsed_results = [] for item in weibo_list: parsed_item = { 'id': item.get('id'), 'text': item.get('text_raw'), # 原始正文,无HTML标签 'created_at': item.get('created_at'), 'reposts_count': item.get('reposts_count'), 'comments_count': item.get('comments_count'), 'attitudes_count': item.get('attitudes_count'), 'user': item.get('user', {}).get('screen_name') } # 清洗文本:去除多余空格、换行,处理表情符号(可能被转义) parsed_item['text'] = ' '.join(parsed_item['text'].split()) parsed_results.append(parsed_item) return parsed_results

数据清洗包括:去除HTML标签、处理Unicode表情(如\u0001f600)、统一时间格式、处理缺失值等。一个干净的、结构化的数据集是后续分析的前提。

4. 反反爬策略与稳健性设计

微博这类平台有完善的风控体系。一个“裸奔”的爬虫很快会被封IP或要求验证码。

4.1 请求头(Headers)的伪装

这是最基本也最重要的一步。你的请求头不能只是简单的User-Agent。需要模拟一个真实浏览器的完整请求头集合。

headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8', 'Accept-Encoding': 'gzip, deflate, br', 'Referer': 'https://weibo.com/', # 正确设置来源页 'Connection': 'keep-alive', 'Upgrade-Insecure-Requests': '1', 'Sec-Fetch-Dest': 'document', 'Sec-Fetch-Mode': 'navigate', 'Sec-Fetch-Site': 'same-origin', 'Sec-Fetch-User': '?1', 'Cache-Control': 'max-age=0', }

心得Referer字段非常重要,很多接口会校验它。通常将其设置为目标页面的上级页面URL。User-Agent可以准备一个池子,轮流使用。

4.2 访问频率控制

这是体现“道德爬虫”的关键。绝对不能无间隔地疯狂请求。

import time import random def request_with_delay(url, session): """带随机延迟的请求""" # 随机延迟1-3秒,模拟人类阅读间隔 delay = random.uniform(1, 3) time.sleep(delay) response = session.get(url, headers=headers) return response

对于大规模抓取,建议将延迟设置得更长(如3-10秒),并且最好将抓取任务分散到不同时间段进行。

4.3 代理IP池的使用

当单个IP请求过于频繁被限制后,就需要切换IP。可以购买付费代理服务,或者自建代理池。在下载器中集成代理逻辑:

from proxy_pool import get_proxy # 假设有一个获取代理的函数 def download_with_proxy(url, max_retries=3): for attempt in range(max_retries): proxy = get_proxy() # 获取一个代理IP,格式如 {'http': 'http://1.2.3.4:8080'} try: response = requests.get(url, headers=headers, proxies=proxy, timeout=10) if response.status_code == 200: return response else: # 可能代理IP无效,标记并重试 mark_proxy_failed(proxy) except Exception as e: mark_proxy_failed(proxy) continue raise Exception(f"Failed to fetch {url} after {max_retries} retries.")

4.4 异常处理与状态监控

一个健壮的爬虫必须能处理各种异常:网络超时、连接错误、HTTP状态码异常(403、404、500)、解析失败等。

try: response = session.get(url, timeout=15) response.raise_for_status() # 如果状态码不是200,抛出HTTPError异常 # 解析数据 data = parse_response(response) except requests.exceptions.Timeout: log.error(f"请求超时: {url}") # 加入重试队列 except requests.exceptions.HTTPError as e: log.error(f"HTTP错误 {e.response.status_code}: {url}") if e.response.status_code == 403: log.warning("可能触发了反爬,需要检查Cookie或代理。") # 触发反爬应对策略,如更换Cookie、代理,或休眠更长时间 except Exception as e: log.error(f"未知错误: {e}") finally: # 确保资源释放等清理工作 pass

同时,需要建立日志系统,记录每次请求的URL、状态、耗时、数据量,便于后期排查问题和优化性能。

5. 数据存储与管道设计

解析后的数据需要有效存储。我们设计了可插拔的管道(Pipeline)模式。

5.1 文件存储(CSV/JSON)

适用于中小规模数据或快速原型验证。

import csv import json from datetime import datetime class CsvPipeline: def __init__(self, filename='weibo_data.csv'): self.filename = filename self.file = open(filename, 'a', newline='', encoding='utf-8-sig') self.writer = None self.fieldnames = None def open_spider(self): pass def process_item(self, item): if not self.fieldnames: self.fieldnames = item.keys() self.writer = csv.DictWriter(self.file, fieldnames=self.fieldnames) self.writer.writeheader() self.writer.writerow(item) return item def close_spider(self): self.file.close() class JsonPipeline: def __init__(self, filename='weibo_data.json'): self.filename = filename self.data = [] def process_item(self, item): self.data.append(item) return item def close_spider(self): with open(self.filename, 'w', encoding='utf-8') as f: json.dump(self.data, f, ensure_ascii=False, indent=2)

5.2 数据库存储(MySQL/MongoDB)

适用于大规模、需要复杂查询的数据。

import pymysql from DBUtils.PooledDB import PooledDB # 使用连接池提升性能 class MySQLPipeline: def __init__(self, db_config): self.pool = PooledDB( creator=pymysql, maxconnections=5, **db_config ) self._create_table_if_not_exists() def _create_table_if_not_exists(self): create_sql = """ CREATE TABLE IF NOT EXISTS weibos ( id BIGINT PRIMARY KEY, user_id BIGINT, content TEXT, created_at DATETIME, reposts_count INT, comments_count INT, attitudes_count INT, crawl_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) """ conn = self.pool.connection() with conn.cursor() as cursor: cursor.execute(create_sql) conn.commit() conn.close() def process_item(self, item): insert_sql = """ INSERT INTO weibos (id, user_id, content, created_at, reposts_count, comments_count, attitudes_count) VALUES (%s, %s, %s, %s, %s, %s, %s) ON DUPLICATE KEY UPDATE reposts_count=VALUES(reposts_count), comments_count=VALUES(comments_count), attitudes_count=VALUES(attitudes_count) """ conn = self.pool.connection() with conn.cursor() as cursor: cursor.execute(insert_sql, ( item['id'], item['user_id'], item['content'], item['created_at'], item['reposts_count'], item['comments_count'], item['attitudes_count'] )) conn.commit() conn.close() return item

使用ON DUPLICATE KEY UPDATE语句可以避免重复插入相同微博,只更新互动数据。

5.3 异步与分布式扩展思考

当抓取目标极多时,单机单线程效率低下。可以考虑:

  • 异步爬虫:使用aiohttp+asyncio库,实现高并发IO请求,能极大提升抓取速度(尤其对于API接口)。
  • 分布式爬虫:使用Scrapy-Redis框架或Celery任务队列,将URL调度、请求下载、数据解析等任务分发到多台机器上执行。核心在于共享一个去重队列(如Redis)和统一的结果存储。

6. 常见问题排查与实战心得

6.1 问题速查表

问题现象可能原因排查步骤与解决方案
返回空白页或状态码4031. Cookie失效
2. IP被限制
3. 请求头不完整或被识别
1. 检查并更新Cookie。
2. 更换代理IP,并增加请求延迟。
3. 使用浏览器开发者工具,对比真实请求与爬虫请求的Headers差异,特别是User-Agent,Referer,Cookie
能拿到HTML但解析不到数据1. 页面结构已更新,XPath/CSS选择器失效
2. 数据通过JS动态加载,初始HTML中没有
1. 重新分析页面,更新解析规则。
2. 使用Selenium渲染页面,或直接查找并调用对应的数据API接口。
API接口返回“请求参数错误”接口参数格式或签名已变更1. 重新抓包分析最新请求参数。
2. 检查是否有时间戳、随机数、签名(sign)等动态参数,并逆向其生成算法。
数据抓取速度很慢1. 请求间隔设置过长
2. 过度依赖Selenium
3. 网络或代理延迟高
1. 在遵守道德和法规前提下,适当优化延迟逻辑(如首次请求后动态调整)。
2. 尽可能使用轻量的Requests调用API。
3. 测试代理IP质量,更换优质代理。
数据库写入失败或重复1. 数据库连接异常
2. 数据主键冲突
1. 检查数据库配置、网络和连接池状态。
2. 使用INSERT ... ON DUPLICATE KEY UPDATE或先查询后插入的逻辑处理重复数据。

6.2 核心心得与避坑指南

  1. 尊重robots.txt与法律法规:这是红线。只抓取公开的、非敏感的数据,并明确你的使用目的符合相关规定。在代码中设置合理的请求间隔,避免对目标服务器造成攻击性负载。
  2. Cookie管理是生命线:对于需要登录态的爬虫,Cookie的有效性直接决定爬虫寿命。建立Cookie池,定期验证和更新,比死磕模拟登录更稳定。
  3. 拥抱API,远离HTML解析:只要有可能,优先寻找并调用数据接口。JSON数据干净、结构化好、体积小,解析效率远高于从杂乱的HTML中抽取信息。
  4. Selenium是最后的武器:它强大但笨重、不稳定。仅在动态渲染必不可少时使用,并务必使用无头模式、显式等待,用完即关。
  5. 日志要详细:记录下每个请求的URL、状态、耗时、响应大小。当出现问题时,详细的日志是唯一的排查线索。建议使用logging模块,并区分INFOWARNINGERROR等级别。
  6. 设计要有弹性:将爬虫设计成模块化、可配置的。反爬策略会变,页面结构会改。一个良好的设计能让你在应对变化时,只需修改最小范围的代码。
  7. 数据质量高于数据数量:在存储前进行必要的数据清洗和验证。一个包含大量空值、错误格式的数据集,其分析价值会大打折扣。考虑使用pandas进行初步的数据质量检查。

回顾这个“SinaWeiboSpider”项目,它的源码本身可能已不能直接运行于今天的微博,但其分层架构的设计思想、混合请求的策略、针对反爬的应对措施,以及数据解析与存储的工程化实践,仍然是构建一个稳健、高效网络爬虫的通用蓝图。爬虫技术是与网站风控不断博弈的过程,其核心不在于找到一套一劳永逸的代码,而在于掌握一套分析问题、拆解问题、设计解决方案的方法论。希望这次对旧项目源码的深度拆解,能为你开启自己的数据采集项目提供扎实的参考和启发。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询