尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
Python 爬虫标题抓取全攻略:从 requests 到 Playwright 的完整实践
简介这是一份面向Python初学者的爬虫实战资料核心任务为从目标网页提取全部文章标题。资源完整演示了使用requests库发送请求、借助BeautifulSoup解析网页结构通过定位二级标题标签收集标题文本最终逐行写入本地文件。代码按功能拆分为抓取与存储两个函数便于理解和二次开发同时加入请求状态判断与空结果保护并给出修改选择器以适应不同站点的建议。整个资源包仅含一个docx文档大小约17KB但系统讲解了环境安装、核心函数作用、运行示例及合规注意事项。目前已有174人学习下载适合有基本编程基础、想掌握网页信息采集入门技能的开发者。跟随示例实际动手修改目标地址与标签定位方式就能快速迁移到新闻、博客等场景为后续更复杂的爬虫开发打下扎实基础。1. 抓取文章标题从单页到批量Python 的完整方案文章标题抓取是几乎所有内容型爬虫项目的第一步。内容聚合、竞品文章监控、行业资料归档、舆情关键词追踪这些需求最终都要落到一个动作上把目标页面里的标题文字准确取出来。标题字段看起来简单实际做起来却有不少反直觉的地方——大多数人以为难点在提取规则真正翻车时才发现是编码错乱、动态渲染、反爬限速在作怪。这篇笔记我从一个可以直接跑的最小实现讲起逐步加入反爬、动态页面处理、批量调度和标题清洗一路写到踩坑记录。适合刚写完第一个 requests 请求、准备做批量抓取的开发者也适合被解析稳定性折磨过的熟手对照检查自己的方案。2. 静态页面标题抓取requests 解析库的最小可用实现2.1 为什么先选 requests BeautifulSoup请求与解析的分工抓取标题本质是两个独立环节。请求环节负责把 HTML 拿回来解析环节负责从 HTML 里把标题文字抠出来。把这两件事分开想选型就清楚了——requests 是 Python 生态里最成熟的 HTTP 客户端处理超时、重定向、session 复用都不需要额外造轮子BeautifulSoup 则是解析库它不关心网络只负责把拿到的 HTML 字符串变成可查询的结构。常见误区是有人用 urllib 手写请求结果被重定向、gzip 压缩、Cookie 维持这些问题反复折磨。我的建议很直接请求一律 requests解析按页面复杂度从 BeautifulSoup 起步。原因是 BeautifulSoup 的 find/find_all 对新手足够友好缩进错乱的 HTML 也能容错解析而 lxml 的 XPath 虽然性能更好但遇到不规范页面时写错一个路径就全军覆没。先跑通再优化这是做爬虫的基本节奏。2.2 抓取单页标题最小命令与逐行说明先看一个能直接运行的最小代码目标是抓取任意一个静态页面的标题。这里以某新闻站的列表页为例页面源码里标题同时出现在title和h1中。import requests from bs4 import BeautifulSoup url https://example-news-site.com/list headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 } resp requests.get(url, headersheaders, timeout10) resp.encoding resp.apparent_encoding # 用响应内容推断编码避免乱码 soup BeautifulSoup(resp.text, html.parser) title soup.title.string.strip() if soup.title else print(f页面标题: {title})代码逻辑分四步构造请求头、发起 GET 请求、用 BeautifulSoup 解析响应文本、取title标签内容。第三行的 timeout 参数是必调的——默认情况下 requests 会一直等下去批量抓取时一个卡死的连接就能拖垮整个任务。resp.apparent_encoding是从内容里检测编码比直接信任响应头里的 charset 可靠后面避坑章节还会细说。这里有个取舍要注意title是浏览器标签页上显示的名字长度通常被截断h1是页面正文的大标题语义更完整。真实项目里我一般优先取title做归档因为它包含站点名后缀比如某某新闻 - 官网利于区分来源如果title为空再回退到h1。下面的参数表给出两种标签的抓取写法标题位置抓取代码适用场景title标签soup.title.string通用方案所有页面都有h1标签soup.find(h1).get_text()正文标题更完整但页面可能缺失meta的 og:titlesoup.find(meta, propertyog:title)[content]社交分享场景有些站点只在此处写完整标题2.3 批量抓取循环、限速与失败重试的骨架单页跑通后下一步就是批量。批量抓取的代码骨架比单页多三样东西循环、延时、异常处理。很多人直接写一个 for 循环就上结果跑了几十页就被封 IP甚至把目标站点打到响应 503。延时不是可选项是保命项。import time import random import requests from bs4 import BeautifulSoup urls [ https://example-news-site.com/list/1, https://example-news-site.com/list/2, https://example-news-site.com/list/3, ] session requests.Session() session.headers.update({ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 }) for url in urls: try: resp session.get(url, timeout10) if resp.status_code ! 200: print(f跳过 {url}状态码 {resp.status_code}) continue resp.encoding resp.apparent_encoding soup BeautifulSoup(resp.text, html.parser) title soup.title.string.strip() if soup.title else print(f{url} - {title}) except requests.RequestException as e: print(f请求失败 {url}: {e}) time.sleep(random.uniform(1.5, 3.5)) # 关键随机延时第 19 行的random.uniform(1.5, 3.5)是最值得抄的参数。固定延时比如每次都 sleep 2 秒会被频率统计轻松识别随机延时让请求间隔呈自然分布。Session 的作用是复用底层的 TCP 连接批量请求时能明显减少握手开销同时自动携带上一次响应留下的 Cookie。这里我把status_code ! 200直接跳过而不是抛异常是因为标题抓取场景里 404、403 都是常态不值得为单个页面中断整个任务。这段代码的问题也明显失败就跳过没有重试。生产级任务要在异常时做指数退避重试后面第 3 章专门讲。2.4 用 XPath 还是正则三种解析方式的选型比较BeautifulSoup 不是唯一选择。我见过不少老项目用正则硬抠标题代码长得像天书页面结构一变就彻底失效。这里把三种方案摆在一起对比按实际项目里的选型顺序说。正则表达式适合结构极其稳定的小片段比如从 JS 变量里取标题re.search(rtitle[\]?\s*:\s*[\](.*?)[\], html)。优点是快、无需解析整个 DOM缺点是脆弱——引号转义、多行文本、HTML 实体都能让正则翻车。BeautifulSoup 适合标签结构清晰的页面用find(h1)这种语义化写法页面微调时容错性好。lxml 的 XPath 适合复杂页面里需要按层级定位的场景比如//div[classarticle]//h1[1]/text()性能最好但依赖精确的 class 命名前端改个类名就断。我的经验是遵循一个递进原则先试 BeautifulSoup 的标签名定位不行再试 CSS 选择器soup.select()其实比 find 更简洁最后才考虑 XPath 和正则。不要一上来就用最精确的路径越精确越脆弱。另外解析性能在标题抓取场景里基本不是瓶颈——请求网络的时间比解析多几个数量级用 lxml 换来的毫秒级提升没有意义代码可维护性才是长期成本。3. 反爬与请求策略让批量抓取不至于被封3.1 常见反爬手段的现实分层反爬不是玄学它有清晰的层次。第一层看 User-Agent 和 Referer目标是拦截脚本默认标识第二层看请求频率和访问模式检测短时间大量请求的 IP第三层是动态渲染和指纹追踪页面内容由 JavaScript 生成直接请求返回的是空壳。标题抓取场景里90% 的封禁都停留在前两层正确处理 UA 和频率就够用了。有个血泪经验值得先说不要一开始就上代理池。代理池的维护成本极高免费代理的失效率和延迟会让排查问题变得非常痛苦。很多开发者一被限制就怀疑 IP 不够干净实际上日志里多半写着 UA 是python-requests或者每秒请求数超过阈值。先把自己这边的请求伪装做好再判断是否需要换 IP。3.2 请求头伪装与随机 UA 的实现用户代理是服务器判断客户端身份的第一依据。requests 默认的 UA 是python-requests/x.y.z任何反爬系统看到这个都会直接警觉。伪装 UA 的正确做法不是写死一个浏览器 UA而是维护一个列表、每次请求随机选取。import random import requests USER_AGENTS [ Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36, Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36, Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36, ] def get_session(): session requests.Session() session.headers.update({ User-Agent: random.choice(USER_AGENTS), Accept: text/html,application/xhtmlxml,application/xml;q0.9,image/avif,image/webp,*/*;q0.8, Accept-Language: zh-CN,zh;q0.9,en;q0.8, Connection: keep-alive, }) return sessionrandom.choice 每次调用返回不同 UA防止同一个会话重复使用相同标识。Accept 和 Accept-Language 也要配因为有些服务端会根据 Accept-Language 直接拒绝非中文客户端的请求。这里有个细节Connection: keep-alive配合 Session 使用能减少重复建连的握手次数但不建议在每次请求都新建 Session——Session 的生命周期应该覆盖一批连续请求。3.3 限速与指数退避重试两个必调参数随机 UA 解决身份伪装限速解决频率检测。上一章的随机延时已经覆盖了基础限速但生产场景还需要重试机制。重试不是简单地再请求一次要用指数退避——第一次失败等 1 秒第二次等 2 秒第三次等 4 秒让服务端的封禁判断有时间解除。import time import random import requests from requests.adapters import HTTPAdapter session requests.Session() session.headers.update({User-Agent: Mozilla/5.0}) retries 3 for attempt in range(retries): try: resp session.get(url, timeout10) if resp.status_code 429 or resp.status_code 503: wait 2 ** attempt random.uniform(0, 1) print(f触发限流等待 {wait:.1f} 秒后重试) time.sleep(wait) continue resp.raise_for_status() break except requests.RequestException as e: if attempt retries - 1: raise wait 2 ** attempt random.uniform(0, 1) time.sleep(wait)这段代码的关键是 sleep 时长用2 ** attempt计算这是标准的指数退避公式。加random.uniform(0, 1)是为了避免多个线程同时等待相同时间后一起重试造成所谓的惊群效应。429 Too Many Requests和503 Service Unavailable是限流最常见的两个状态码识别它们比识别超时更有针对性。另一个必调参数是连接池的大小。requests 默认的池上限是 10批量抓取并发超过这个数时多余的请求会排队而不是立即发出。用HTTPAdapter(pool_connections20, pool_maxsize20)可以提升并发上限但注意池子越大单瞬间对目标的压力也越大scale up 要和延时参数配套调整。3.4 代理与 Cookie 策略什么时候才需要代理和 Cookie 属于重武器不是默认配置。代理的适用场景是目标站点对单个 IP 的请求量有严格上限且你已经把 UA、限速、重试都优化到位确认瓶颈确实是 IP 维度。Cookie 的适用场景是目标页面需要登录才能看到标题列表或者是首次访问会种下访问令牌、直接请求拿不到完整内容。Cookie 处理我建议优先用 Session 自动维持而不是手动拼 Cookie 头。先用普通请求访问首页拿到初始 Cookie再带着这个 Session 访问目标列表页代码上就是把所有请求都走同一个 session 对象。如果需要登录态可以用 requests 的session.post()先提交登录表单后续请求自动携带认证 Cookie。手动维护 Cookie 的过期时间是一件非常容易踩坑的事我见过有人把过期 Cookie 写死在代码里第二天任务全部 403 才排查出来。代理的实现方式是给 session 设置proxies参数写法是session.proxies {http: http://ip:port, https: http://ip:port}。真到了需要代理这一步要选支持 HTTPS 的代理否则加密流量会被降级或拒绝。但请记住前面说过的话——代理是最后手段不是第一选择。4. 动态页面标题抓取Selenium 与 Playwright 的选择4.1 为什么有些页面请求拿不到标题用 requests 请求一个页面拿到的响应文本里可能根本没有标题。这不是编码问题而是页面内容由 JavaScript 在浏览器里动态生成。常见的有三类SPA 应用整个 HTML 是一个空壳所有内容靠前端路由渲染、异步加载标题在页面加载完成后通过 XHR 请求填充、A/B 测试服务端根据 JS 执行结果决定渲染哪个标题。用 requests 拿到的只是 JavaScript 执行前的初始 HTML。这时候的选项是模拟浏览器执行 JavaScript。Selenium 是传统选择通过 WebDriver 驱动真实浏览器Playwright 是后起之秀API 设计更现代内置等待机制和自动重试。如果从零开始一个新项目我会直接选 Playwright它的wait_for_selector可以精确等待标题节点出现不用像 Selenium 那样手动 sleep。4.2 Playwright 抓取渲染后标题的最小代码下面是用 Playwright 同步 API 抓取动态页面标题的最小实现。核心差异在于它启动了一个 Chromium 实例页面真正执行了 JavaScript。from playwright.sync_api import sync_playwright url https://example-spa-site.com/article/1001 with sync_playwright() as p: browser p.chromium.launch(headlessTrue) page browser.new_page() page.goto(url, timeout30000, wait_untildomcontentloaded) page.wait_for_selector(h1) # 等待标题节点出现 title page.locator(h1).inner_text() browser.close() print(f渲染后标题: {title})这段代码的逻辑顺序是启动浏览器实例、新建页面、跳转 URL、等待 h1 节点出现、提取文本。playwright 的wait_for_selector是最关键的一行它放弃了固定等几秒的坏习惯改为轮询检查选择器是否出现页面渲染快就早返回渲染慢也不会过早报错。timeout 参数设定等待上限超时则抛出异常。无头模式的 headlessTrue 表示浏览器不显示窗口。注意 Playwright 需要先安装浏览器内核常见做法是项目里执行playwright install chromium。首次运行时这条命令会下载约 100MB 的二进制文件CI 环境里要提前缓存否则每次构建都重新下载。4.3 无头浏览器是重武器性能与资源边界引入 Playwright 之后要警惕资源消耗。一个 Chromium 实例的内存占用在 200MB 到 500MB 之间并发开 5 个实例就能吃掉普通开发机的内存。所以能静态解析的页面不要用浏览器渲染动态页面也要限制并发数。性能调优的三个参数值得记下来第一是并发实例数控制在 2 到 3 个多了会触发目标站点的更激进的反爬第二是请求超时Playwright 的默认导航超时是 30 秒批量任务里可以调到 15 秒因为等 30 秒渲染一个已经不存在的标题没有意义第三是网络空闲等待page.goto(..., wait_untilnetworkidle)会等待所有网络请求完成但这会显著拖慢速度建议只在确实需要等某个异步请求时才用。动态渲染和静态解析还有一个互补思路先用 requests 快速试探发现响应里没有目标标题后才降级到 Playwright。这种静态优先、动态兜底的策略能节省大量时间和内存是生产环境里我比较建议的架构。判断依据很简单——解析出的 HTML 文本长度远小于正常页面或者搜索不到h1标签时触发降级。5. 抓取标题的避坑指南从解析失败到编码错乱5.1 编码错乱让人抓狂全是乱码的标题不是没抓到是没解码现象打印出来的标题是鏂伴椈棣栭〉这类乱码或者出现一堆菱形问号。原因服务器返回的 Content-Type 头写了 utf-8但页面实际内容用的是 GBK或者响应头里根本没指定 charsetrequests 默认从 HTTP 头推断失败后用了错误的编码。解决统一用resp.apparent_encoding覆盖resp.encoding这个属性会读取页面中的 meta 声明和内容特征做编码探测而不是盲目信任响应头。如果这招还不行就用resp.content.decode(gbk, errorsignore)手工尝试目标站点的实际编码。5.2 解析到空标题不只是选择器写错现象BeautifulSoup 返回的 title 是 None或者解析出来是空字符串但浏览器里页面明明有标题。原因除了选择器本身写错更隐蔽的原因是页面通过noscript标签给不支持 JS 的浏览器展示了空内容或者标题文本嵌在注释里!-- h1某标题/h1 --BeautifulSoup 默认解析会忽略注释但如果你用正则去匹配就会把注释里的假标题抠出来。解决先打印soup.prettify()看看解析后的结构确认标题节点真实存在且不在注释内。用soup.find(h1)拿不到时换成soup.find(meta, propertyog:title)取 meta 内容这是很多站点的保底方案。5.3 请求超时与连接池耗尽批量任务的高频事故现象批量任务跑到一半日志里报出大量Connection timed out或者Max retries exceeded之后几乎每个请求都失败。原因两个可能。一是目标服务器开始主动断连限流信号二是本地连接池里的连接因为长期空闲被服务端关闭requests 复用已失效的 keep-alive 连接导致报错。解决给 Session 挂上HTTPAdapter(max_retries3)让连接失败自动重连同时确认 requests 的timeout同时设置了连接超时和读取超时timeout(5, 15)这比单个数字更精确。如果错误集中出现在运行后期优先检查是否触发了限流而不是本地代码。5.4 标题里夹带 HTML 实体看起来对实际没法用现象抓到的标题是2024#x5e74;#x6700;#x65b0;Apple#x6536;#x8d2d;#x6d88;#x606f;存进数据库后前端不解析就显示一串乱码。原因页面里的标题文本被编码成了 HTML 实体BeautifulSoup.get_text()有时能自动解码但如果用了str(tag.string)直接转字符串实体就被保留下来了。解决用html.unescape(title)统一解码这是 Python 标准库html模块提供的方法一个函数解决所有实体问题。标题清洗环节里这步必须排在前面。6. 从能跑到好用标题清洗、去重与存储的落地习惯抓回来的标题如果直接入库后面做聚合展示时会非常痛苦。同一篇文章可能被不同站点转载标题带了各自的站点后缀同一篇的标题在某站是重磅某某发布新政策另一个站是某某发布新政策全文。所以标题抓取的最后一公里不是在网络上而是在数据处理上。我一般会按三步处理。先是基础清洗strip()去掉首尾空白换行符和全角空格用re.sub(r[\s\u3000], , title)统一替换成半角空格然后用html.unescape()解掉 HTML 实体顺序不能反过来先解实体再规整空白否则nbsp;解出来的是普通空格再替换一次纯属多此一举。然后是去重最简单的场景用哈希集合title.strip()后的字符串直接作为 key 存进 set重复就跳过。但实践中不同站点的后缀会导致同一文章算不出相同哈希所以标题去重前要先做归一化——把| 某某网、 - 某某官网这类站点后缀用正则剥掉甚至可以把所有非中文字符去掉再计算哈希这样跨站转载也能识别。最后的存储量小用 SQLite 加唯一索引量大再考虑 PostgreSQL但字段设计上务必把url、title、source_site、抓取时间分开存方便以后按来源统计和增量抓取。这套清洗流程的边界也很清楚它适合标题规范化不适合做内容去重。如果目标是判断两篇文章是否同一篇只靠标题远远不够还要比较正文的文本指纹。我这里用的哈希去重是保守方案宁可漏掉一些变体转载也不要把不是同一篇的标题误删。做爬虫这几年我最大的教训是永远不要假设目标页面的结构会一直不变。今天的h1明天可能换成div classarticle-title今天的编码探测正常明天可能涌进来一页乱码。所以我现在养成的习惯是每次批量任务开始前先抓三个页面做结构自检解析结果不符合预期就发告警而不是直接跑完整批。这个习惯帮我避开了好多次跑了一小时发现前半小时的数据全是空的的尴尬场景。希望这篇笔记能帮你在标题抓取这件事上少走几步弯路先从最小代码跑通再一步步把策略补上踩坑时回头对照第 5 章的排查思路大概率能找到原因。本文还有配套的精品资源点击获取
RELATED

相关推荐

第一次使用HyprMod:Pending Changes待保存页与全局搜索,新手必知的5个操作技巧

第一次使用HyprMod:Pending Changes待保存页与全局搜索,新手必知的5个操作技巧

【免费下载链接】hyprmod Native GTK4/libadwaita settings app for Hyprland. 项目地址: https://gitcode.com/gh_mirrors/hy/hyprmod 点击查看 免费下载 HyprMod 是一款面向 Hyprland 平铺合成器的原生图形化配置工具,基于 GTK4 和 libadwaita 构建&a…

📅 2026/10/15 18:04:06
FANUC机器人伺服焊枪配网配置详解:从通信架构到实操避坑

FANUC机器人伺服焊枪配网配置详解:从通信架构到实操避坑

简介:面向工业机器人集成与自动化工程师的FANUC伺服焊枪配置及配网技术文档,围绕点焊机器人伺服焊枪的添加、运动方向设置、零点标定、自动调整、厚度检查与压力标定等全流程展开,同时涵盖PROFINET网络下的配网组态、DO/DI信号与DCS配置&…

📅 2026/10/15 18:04:06
FPGA实战:N.5分频器的Verilog实现与占空比优化

FPGA实战:N.5分频器的Verilog实现与占空比优化

做数字电路或者FPGA开发,迟早会碰到一个不大不小的问题:系统里只有 100MHz 的参考时钟,可某个模块就是点名要 40MHz。100 除以 40 等于 2.5,这不是整数分频能解决的事,于是就得用 N.5 分频。N.5 分频的核心难点不在于“…

📅 2026/10/15 17:59:06
MORE NEWS

更多资讯

📰

从 neat-annotations 学现代 CSS 新特性:light-dark()、oklch 与 @property 彩虹动画实战

【免费下载链接】neat-annotations Hand-drawn CSS annotations for inline content 项目地址: https://gitcode.com/gh_mirrors/ne/neat-annotations 点击查看 免费下载 neat-annotations 是一个纯 CSS 实现的手绘风格网页注释库,没有 JavaScript、没有…

📰

20260331 基于物联网技术的河流水位监测系统设计

https://pan.baidu.com/s/1IicYYxJikK4Y4nRBqE4Tiw?pwd9efyhttps://pan.baidu.com/s/1IicYYxJikK4Y4nRBqE4Tiw?pwd9efy本课题围绕河流水位的实时监测与远程管理,基于物联网技术对系统进行整体设计与实现,主要研究内容如下: (1&a…

📰

S7-PLCSIM V17仿真调试完全指南:版本、监控与排错技巧

简介:一份面向西门子 PLC 工程师与自动化初学者的 S7-PLCSIM V17 中文帮助文档与操作说明合集,用于解决 PLC 模拟仿真环境搭建、程序调试及与 TIA Portal/STEP 7 集成使用中的常见疑问。压缩包内仅含 1 个 PDF 文件,容量约 25.95MB&#xff0…

📰

嵌入式场景下用C语言手写HTML解析器:状态机设计与容错实践

简介:这是一份面向C/C初学者与网页解析入门者的轻量级HTML解析实现,核心代码仅30余行,便于快速理解标记语言解析的基本原理。资源包共72个文件,以cpp与h源码文件为主,辅以obj、pdb、ilk等Visual Studio编译中间产物&am…

📰

Qt RTSP播放器实战:FFmpeg解码与低延迟优化

简介:本资源面向具备一定C与Qt基础的开发者,提供一套基于Qt、RTSP协议与FFmpeg库实现的视频流播放器完整工程,可用于远程监控、视频会议、在线教育等需要多路实时视频播放的场景。工程重点实现三通道视频流同时播放与截图功能,通过…

📰

SHT3x温湿度传感器驱动开发:从原理到避坑全指南

简介:SHT3x 温湿度传感器代码包面向嵌入式开发初学者与物联网应用工程师,围绕盛思锐 SHT3x 高精度温湿度传感器,提供一套基于 STM32 平台的完整示例工程,帮助解决环境监测、智能家居、工业自动化等场景中温湿度数据精确采集与解析…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬