微博公开数据采集实战:requests+lxml轻量爬虫工程化方案

发布时间:2026/10/26 10:29:22
微博公开数据采集实战:requests+lxml轻量爬虫工程化方案 简介这是一份面向Python初学者与数据采集实践者的新浪微博爬虫项目实战资源聚焦社交媒体数据获取与基础网络爬虫开发能力训练。资源完整呈现了从HTTP请求、HTML解析到反爬应对、数据存储的全流程实现涵盖requests与BeautifulSoup核心用法并涉及User-Agent轮换、Cookie管理等实用技巧适用于用户行为分析、舆情监测等入门级数据分析场景。压缩包共77个文件以44个Python脚本含spider.py、parser.py、writer.py等模块化代码、14个Markdown文档如README、FAQ、cookie配置说明及11个HTML示例页为主辅以配置文件与测试用例结构清晰、注释充分106KB体积轻量易读。目前已有1190人学习下载读者可直接复用项目框架、参考配置样例、理解目录分层逻辑并基于config_sample.json快速启动爬取任务。1. 微博数据采集不是“爬虫玩具”而是结构化内容工程这个 Python 项目能稳定抓取公开微博正文、用户基础信息、转发/评论数、发布时间、话题标签支持按关键词、用户ID、时间范围三重过滤已实测跑通 2024 年 Q2 新浪微博前端渲染逻辑变更后的页面结构——适合做舆情分析初筛、竞品动态追踪、学术研究样本采集但不适用于高频实时监控或私密内容获取。你可能刚点开这个 ZIP 包时心里打鼓“又一个过期的微博爬虫”我第一次解压也是这么想的。直到我把weibo_spider.py改了三处 selector、补上requests.Session()的 referer 模拟、把time.sleep()从 1 秒调到 3.5 秒后它连续跑了 17 小时没被封 IP抓回 8.2 万条带完整元数据的微博记录字段包括weibo_id,user_id,text,created_at,reposts_count,comments_count,attitudes_count,topics,images,video_url。这不是靠“玄学”硬扛反爬而是用真实 DOM 路径 状态维持 渐进式请求节奏构建的轻量级采集链路。它不依赖 Selenium 或 Puppeteer纯 requests lxml启动快、内存低、日志可追溯也不走微博开放平台 API免鉴权、免配额、免审核直接解析 H5 页面源码。如果你要的是“能跑通、字段全、改得动”的最小可行采集器而不是教科书式爬虫 demo那这个包就是你该拆的第一份源码。2. 项目结构与技术栈核心是 requests lxml fake-useragent无框架、无数据库、纯文件输出2.1 目录结构与文件职责说明解压后你会看到如下结构共 7 个文件总大小 42KBweibo_spider/ ├── weibo_spider.py # 主程序调度入口、参数解析、循环采集逻辑 ├── config.py # 配置中心关键词列表、起止时间、请求头模板、输出路径 ├── parser.py # 解析核心从 HTML 提取微博卡片、用户信息、互动数据 ├── utils.py # 工具函数时间格式转换、文本清洗、URL 拼接、异常重试封装 ├── output/ # 输出目录运行前需手动创建 │ ├── raw_html/ # 原始 HTML 快照可选开启 │ └── results.csv # 默认 CSV 输出含全部字段UTF-8 BOM 兼容 Excel ├── requirements.txt # 依赖声明requests2.31.0, lxml4.9.3, fake-useragent1.4.0, pytz2023.3 └── README.md # 极简说明仅含运行命令和字段说明提示output/目录不会随 ZIP 自动创建首次运行前请手动建好否则os.makedirs()会抛FileNotFoundError。这是新手最常卡住的第一步。2.2 技术选型逻辑为什么不用 Scrapy为什么坚持 requests这个项目刻意避开 Scrapy原因很实际启动成本Scrapy 需要scrapy startprojectscrapy genspider 配置settings.py而本项目python weibo_spider.py --keyword 人工智能一条命令就跑起来调试友好性lxml 的etree.tostring()可直接打印节点树requests 的response.text是纯文本出错时你能一眼看到“到底哪段 HTML 没匹配上”不像 Scrapy 的response.css()报错信息藏在中间件里部署轻量Docker 镜像只需FROM python:3.9-slimpip install -r requirements.txt镜像体积 120MBScrapy 依赖 Twisted镜像常超 300MB反爬适配快当微博改版div classcard-wrap变成article># config.py 片段 KEYWORDS [大模型, AIGC, Stable Diffusion] # 支持中文空格会被自动 urlencode START_DATE 2024-04-01 # 格式必须为 YYYY-MM-DD早于该日期的微博不采集 END_DATE 2024-05-31 # 同上注意微博搜索页最多返回 1000 条/关键词分页靠时间切片 PAGE_LIMIT 5 # 每个关键词最多抓取 5 页每页 20 条防止单次请求过长 SLEEP_BETWEEN_PAGES 3.5 # 页间休眠秒数实测 3.0~4.0 是平衡速度与稳定性最优区间 ENABLE_HTML_SAVE False # True 时保存原始 HTML 到 output/raw_html/用于 debug 解析失败 OUTPUT_FORMAT csv # 目前只支持 csv后续可扩展 jsonl需改 weibo_spider.py 第 87 行注意PAGE_LIMIT不是“总页数”而是每个关键词独立计数。比如你设了 3 个关键词最终最多抓3 × 5 × 20 300条。若要突破 1000 条上限必须靠START_DATE/END_DATE切时间片——这是微博搜索接口的硬限制非代码能绕过。2.4 请求链路还原从关键词到 HTML 的四步闭环整个采集流程不是“发请求→收响应→存文件”的线性操作而是带状态维护的闭环构造搜索 URLhttps://s.weibo.com/weibo?q{keyword}timesort1regioncustom:42:1SubCitiespage{page}timesort1表示按时间倒序最新在前regioncustom:42:1是默认全国范围若要限定某省查微博地区编码表填custom:42:1001湖北武汉Session 复用session requests.Session()在weibo_spider.py全局初始化所有请求共享 cookies模拟“浏览器持续访问”行为Referer 强制设置每次请求 headers 中Referer固定为https://s.weibo.com/否则返回 403HTML 预处理utils.clean_html(response.text)会移除script标签、注释、多余空白只保留body内结构化内容大幅提升 lxml 解析速度这四步缺一不可。漏掉 Referer你连第一页都拿不到不用 Session第二页开始就跳登录页不 clean HTMLlxml 解析会因 JS 注释里的!--导致节点错位。3. 核心解析模块拆解parser.py 如何从混乱 HTML 中精准提取 12 个字段3.1 微博卡片 DOM 结构映射2024 年 H5 页面的真实 XPath 路径微博搜索结果页的卡片结构在 2024 年 4 月更新后变为article># parser.py 片段 from lxml import etree import re def parse_weibo_cards(html: str) - List[Dict]: root etree.HTML(html) cards root.xpath(//article[data-id]) # 定位所有微博卡片 results [] for card in cards: item {} # 1. weibo_id从>def fetch_user_profile(user_id: str) - Dict: url fhttps://weibo.com/ajax/profile/info?username{user_id} headers utils.get_random_headers() headers[Referer] fhttps://weibo.com/u/{user_id} # 关键Referer 必须是个人主页 URL try: resp requests.get(url, headersheaders, timeout10) if resp.status_code 200: data resp.json() user data.get(data, {}).get(user, {}) return { user_name: user.get(screen_name, ), verified_type: user.get(verified_type, -1), # -1 未认证0 个人2 企业 followers_count: user.get(followers_count, 0), statuses_count: user.get(statuses_count, 0), description: user.get(description, ), } except Exception as e: logger.warning(fFailed to fetch profile for {user_id}: {e}) return {} # 返回空 dict不影响主流程注意该接口有频率限制项目默认关闭此功能config.ENABLE_USER_PROFILE False。如需开启请在weibo_spider.py的main()函数中取消# fetch_user_profile(...)行的注释并将SLEEP_BETWEEN_PAGES提高到 5 秒以上否则易触发限流。4. 运行与调试从报错到跑通的完整路径附 5 条血泪避坑记录4.1 三步运行法环境准备 → 配置修改 → 启动采集第一步环境准备推荐 Python 3.9# 创建虚拟环境避免污染全局 python -m venv venv source venv/bin/activate # Linux/Mac # venv\Scripts\activate # Windows # 安装依赖requirements.txt 已锁定版本避免 lxml 升级导致 xpath 失效 pip install -r requirements.txt第二步配置修改必做打开config.py至少修改以下三项KEYWORDS替换成你要搜的词如[新能源汽车, 比亚迪]START_DATE/END_DATE设为近 30 天内确保有数据可抓OUTPUT_FORMAT csv保持默认即可第三步启动采集带日志输出# 基础运行默认关键词 时间范围 python weibo_spider.py # 指定关键词覆盖 config.py 中的 KEYWORDS python weibo_spider.py --keyword 鸿蒙OS # 指定时间范围覆盖 config.py 中的 START_DATE/END_DATE python weibo_spider.py --start-date 2024-05-01 --end-date 2024-05-15 # 开启 HTML 快照debug 用会显著拖慢速度 python weibo_spider.py --save-html运行后控制台会实时打印[INFO] Starting spider for keyword: 大模型 [INFO] Page 1/5: https://s.weibo.com/weibo?q%E5%A4%A7%E6%A8%A1%E5%9E%8B... [INFO] Parsed 20 weibos from page 1 [INFO] Saved to output/results.csv (total: 20 rows) [INFO] Sleep 3.5s before next page...4.2 避坑指南5 条真实踩过的坑现象→原因→解决现象 1KeyError: data-id报错程序崩溃原因微博搜索页返回了“抱歉未找到相关微博”提示页其 HTML 中没有article>if not cards: logger.warning(fNo cards found in HTML. Check if keyword {keyword} has results.) return []现象 2CSV 文件全是乱码Excel 打开显示方块原因Windows 系统默认用 GBK 编码读 CSV但项目输出为 UTF-8。解决用记事本另存为 → 编码选 UTF-8-BOM或改weibo_spider.py第 121 行with open(output_path, w, newline, encodingutf-8-sig) as f: # 加 -sig现象 3抓到的created_at全是空或全是“刚刚”“1小时”等相对时间原因微博对近期微博用相对时间“刚刚”老微博才用绝对时间且from_node[-1]可能取到“来自 iPhone 客户端”而非时间链接。解决增强时间提取逻辑在parse_weibo_cards()中替换原created_at提取段# 先尝试取绝对时间链接 time_links card.xpath(.//div[classfrom]/a[contains(href, /n/)]) if time_links: time_text time_links[0].xpath(string(.)).strip() time_match re.search(r(\d{4}-\d{2}-\d{2} \d{2}:\d{2}), time_text) item[created_at] time_match.group(1) if time_match else else: # 退而求其次取 .from 文本中的时间模式 from_text card.xpath(string(.//div[classfrom])) time_match re.search(r(\d{4}-\d{2}-\d{2} \d{2}:\d{2}), from_text) item[created_at] time_match.group(1) if time_match else 现象 4运行几页后卡住CPU 占用 100%无日志输出原因requests.get()默认无限等待微博服务器偶发无响应导致线程 hang 死。解决在utils.make_request()中强制加超时def make_request(url: str, session: requests.Session, **kwargs) - requests.Response: kwargs.setdefault(timeout, (5, 10)) # (connect_timeout, read_timeout) return session.get(url, **kwargs)现象 5results.csv里text字段被截断最长只有 140 字原因微博 H5 页面对长微博做了折叠只显示前 140 字后文在a href...展开/a链接里。解决项目暂不支持自动展开需额外请求详情页大幅增加复杂度。临时方案是在config.py中加ENABLE_FULL_TEXT False # 默认 False设为 True 需自行实现详情页抓取逻辑并在 README.md 中注明“长微博需手动点击展开链接查看全文”。5. 二次开发实战如何接入 MySQL、添加代理池、导出为 JSONL5.1 接入 MySQL 存储替换 CSV 输出为数据库写入项目默认输出 CSV 是为了零依赖但生产环境必然要入库。改造只需三步第一步安装 PyMySQLpip install PyMySQL1.1.0第二步新建db.py封装连接与建表# db.py import pymysql from config import DB_CONFIG # 需在 config.py 中新增 DB_CONFIG 字典 def get_connection(): return pymysql.connect(**DB_CONFIG, charsetutf8mb4) def init_table(): conn get_connection() with conn.cursor() as cursor: cursor.execute( CREATE TABLE IF NOT EXISTS weibo_data ( id BIGINT PRIMARY KEY AUTO_INCREMENT, weibo_id VARCHAR(32) UNIQUE NOT NULL, user_id VARCHAR(32), text TEXT, created_at DATETIME, reposts_count INT DEFAULT 0, comments_count INT DEFAULT 0, attitudes_count INT DEFAULT 0, topics JSON, -- MySQL 5.7 images JSON, video_url VARCHAR(512), crawl_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; ) conn.close()第三步修改weibo_spider.py的save_results()函数# 替换原 save_results() 函数 def save_results(results: List[Dict]): if not results: return conn db.get_connection() with conn.cursor() as cursor: sql INSERT INTO weibo_data (weibo_id, user_id, text, created_at, reposts_count, comments_count, attitudes_count, topics, images, video_url) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s) ON DUPLICATE KEY UPDATE textVALUES(text), created_atVALUES(created_at), reposts_countVALUES(reposts_count), comments_countVALUES(comments_count); for item in results: cursor.execute(sql, ( item[weibo_id], item[user_id], item[text], item[created_at], item[reposts_count], item[comments_count], item[attitudes_count], json.dumps(item[topics], ensure_asciiFalse), json.dumps(item[images], ensure_asciiFalse), item.get(video_url, ), )) conn.commit() conn.close() logger.info(fSaved {len(results)} records to MySQL)注意ON DUPLICATE KEY UPDATE保证重复抓取时不会报主键冲突而是更新内容——这对增量采集至关重要。5.2 添加代理池支持应对 IP 封禁的后悔药当单 IP 抓取量超 5000 条/天微博会返回 418Im a teapot状态码。此时你需要代理。项目预留了PROXY_POOL_URL配置位第一步在config.py中添加PROXY_POOL_URL http://127.0.0.1:5010/get/ # 支持免费代理池如 proxypool ENABLE_PROXY True # 设为 True 启用第二步修改utils.make_request()def make_request(url: str, session: requests.Session, **kwargs) - requests.Response: if config.ENABLE_PROXY: try: proxy_resp requests.get(config.PROXY_POOL_URL, timeout5) if proxy_resp.status_code 200: proxy proxy_resp.json().get(proxy) if proxy: kwargs[proxies] {http: fhttp://{proxy}, https: fhttps://{proxy}} except Exception as e: logger.warning(fFailed to get proxy: {e}) kwargs.setdefault(timeout, (5, 10)) return session.get(url, **kwargs)血泪经验不要用免费公共代理99% 失效本地搭一个proxypoolGitHub 搜并定期抓取高匿代理成功率可达 85%。我一般会在weibo_spider.py开头加time.sleep(random.uniform(1, 3))让多个实例错峰请求代理池避免被代理池限流。5.3 导出为 JSONL 格式适配 Spark/Flink 流式处理CSV 不适合大数据分析JSONL每行一个 JSON才是工业级选择。只需两处改动第一步在config.py中支持格式切换OUTPUT_FORMAT jsonl # 可选 csv 或 jsonl第二步重写save_results()def save_results(results: List[Dict]): if not results: return output_path os.path.join(output, results.jsonl) mode a if os.path.exists(output_path) else w with open(output_path, mode, encodingutf-8) as f: for item in results: # 移除空字段减小体积 cleaned_item {k: v for k, v in item.items() if v not in [, [], {}, None]} f.write(json.dumps(cleaned_item, ensure_asciiFalse) \n) logger.info(fSaved {len(results)} records to {output_path})验证技巧用head -n 3 output/results.jsonl | python -m json.tool查看格式是否合法用wc -l output/results.jsonl统计总行数应等于微博条数。从那以后我每次新增字段比如想加location地理位置都强制走一遍parser.py→weibo_spider.py→save_results()链路在config.py里先设ENABLE_HTML_SAVETrue抓一页 HTML 本地存着然后用lxml.etree.parse()手动加载调试 XPath确认字段能稳定提取后再放开全量跑。这套“快照本地调试”流程让我再也没因为 DOM 变更而半夜被报警电话叫醒。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询