看到竞品改了一轮标题和描述后排名涨了30%,想把它的200个页面标题、描述、H1、结构化数据全部抓出来分析规律,打开Chrome的F12一个页面一个页面复制粘贴到Excel里,搞了一个下午才弄完不到40个页面,而用Python写一个requests+BeautifulSoup的脚本,20分钟就把200个页面的TDK和结构化数据全提取完输出成CSV了
做SEO的站长日常有一半时间在"看别人怎么做"。看竞品的标题怎么写、描述怎么堆、H标签怎么布局、结构化数据用了哪些类型、内链锚文本有什么规律。这些信息都公开在网页HTML里,但问题是一个个手动F12去看效率太低,尤其是竞品有几百上千个页面的时候,手动提取基本不可能完成。
页面抓取工具解决的正是这个问题:把分散在大量网页HTML里的结构化信息,批量提取、清洗、输出成可以分析的表格数据。从竞品分析到站内审计到数据监控,但凡需要"从网页里自动拿数据"的场景,都离不开它。
页面抓取工具选型,四个核心判断维度
| 1 | 目标页面是静态HTML还是JS渲染:静态用requests+解析库就够,JS渲染必须上浏览器自动化(Playwright/Selenium) |
| 2 | 抓取量级有多大:几十个页面浏览器插件就够,几百个用Python脚本,几千上万需要Scrapy框架的并发调度能力 |
| 3 | 提取内容的结构化程度:只提取标题和描述用CSS选择器就行,要提取JSON-LD结构化数据、meta标签、Open Graph等需要多维度解析 |
| 4 | 有没有反爬机制:简单的User-Agent伪装就够,还是需要代理IP轮换、请求频率控制、验证码识别 |
一、从零代码到全代码,四类抓取工具的适用边界
市面上能抓网页的工具少说几十种,但按技术门槛和灵活度来分,可以归为四类。每类的适用场景差了不止一点,选错了要么功能不够用,要么杀鸡用牛刀。
| 工具类型 | 代表工具 | 门槛 | JS渲染 | 适合量级 | 站长典型场景 |
|---|---|---|---|---|---|
| 浏览器扩展 | Web Scraper、Data Miner | 零代码,点点选选 | 天然支持 | 几十到几百页 | 快速提取竞品列表页数据、临时小批量抓取 |
| 可视化桌面工具 | 八爪鱼、后羿采集器 | 低代码,拖拽配置 | 支持 | 几百到几千页 | 不会写代码但要大批量抓取、需要定时自动采集 |
| Python脚本 | requests+BeautifulSoup、Playwright | 需要Python基础 | 可选(Playwright支持) | 几百到几千页 | 自定义提取逻辑、多维度数据清洗、输出定制格式 |
| 爬虫框架 | Scrapy、pyspider | 需要较深开发能力 | 需配合中间件 | 几千到几十万页 | 全站抓取、持续增量采集、需要并发调度和中间件 |
对于大多数站长的日常需求——分析竞品几百个页面的TDK、提取全站结构化数据做审计、批量抓取行业站的内容结构——Python脚本方案是性价比最高的选择。它比浏览器扩展灵活(可以自由定义提取逻辑和数据清洗规则),比可视化工具轻量(不需要安装额外的桌面软件和配置复杂的流程),比Scrapy框架简单(不需要学一整套中间件和Pipeline体系)。

一个实用的判断标准:如果你能说清楚"我要抓哪个网站的哪些字段、大概多少个页面、多久抓一次",那Python脚本就够用。如果你需要"自动发现新页面、自动去重、断点续抓、分布式调度",那才需要上Scrapy框架。90%的站长需求都在Python脚本的能力范围内。
二、静态页面抓取:requests+BeautifulSoup,30行代码搞定TDK提取
如果你的目标页面是传统的服务端渲染(HTML直出,查看源代码能看到完整内容),requests+BeautifulSoup就是最轻量的方案。不需要启动浏览器,不需要安装额外的驱动,两个库搞定所有静态内容的提取。
import csvimport timeimport requestsfrom bs4 import BeautifulSoupdef extract_page_info(url):"""提取单个页面的SEO关键信息"""headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'}try:resp = requests.get(url, headers=headers, timeout=15)resp.encoding = resp.apparent_encoding # 自动检测编码soup = BeautifulSoup(resp.text, 'html.parser')info = {'url': url,'title': soup.title.string.strip() if soup.title else '','meta_description': '','meta_keywords': '','h1': '','h1_count': len(soup.find_all('h1')),'h2_list': [],'canonical': '','word_count': len(soup.get_text().strip()),'internal_links': 0,'external_links': 0,'images_without_alt': 0,'structured_data_types': [],}# 提取meta标签for meta in soup.find_all('meta'):name = meta.get('name', '').lower()prop = meta.get('property', '').lower()content = meta.get('content', '')if name == 'description':info['meta_description'] = contentelif name == 'keywords':info['meta_keywords'] = content# 提取canonicalcanonical_tag = soup.find('link', rel='canonical')if canonical_tag:info['canonical'] = canonical_tag.get('href', '')# 提取H1h1_tag = soup.find('h1')if h1_tag:info['h1'] = h1_tag.get_text().strip()# 提取所有H2info['h2_list'] = [h2.get_text().strip() for h2 in soup.find_all('h2')]# 统计链接for a in soup.find_all('a', href=True):href = a['href']if href.startswith('http'):info['external_links'] += 1else:info['internal_links'] += 1# 统计无alt的图片info['images_without_alt'] = len([img for img in soup.find_all('img') if not img.get('alt')])# 提取JSON-LD结构化数据类型for script in soup.find_all('script', type='application/ld+json'):try:import jsondata = json.loads(script.string)if isinstance(data, dict):info['structured_data_types'].append(data.get('@type', 'unknown'))elif isinstance(data, list):for item in data:if isinstance(item, dict):info['structured_data_types'].append(item.get('@type', 'unknown'))except:passreturn infoexcept Exception as e:print(f'抓取失败 {url}: {e}')return Nonedef batch_extract(url_list, output_csv='seo_analysis.csv'):"""批量抓取并输出CSV"""results = []for i, url in enumerate(url_list):print(f'正在处理 ({i+1}/{len(url_list)}): {url}')info = extract_page_info(url)if info:results.append(info)time.sleep(1.5) # 礼貌的请求间隔# 输出CSVif results:with open(output_csv, 'w', newline='', encoding='utf-8-sig') as f:fieldnames = ['url', 'title', 'meta_description', 'h1', 'h1_count','word_count', 'internal_links', 'external_links','images_without_alt', 'canonical', 'structured_data_types']writer = csv.DictWriter(f, fieldnames=fieldnames, extrasaction='ignore')writer.writeheader()writer.writerows(results)print(f'\n完成!共抓取 {len(results)} 个页面,结果保存至 {output_csv}')return results# 使用示例urls = ['https://example.com/page1','https://example.com/page2',# 从文件读取URL列表# urls = [line.strip() for line in open('urls.txt', 'r') if line.strip()]]results = batch_extract(urls)这段代码做了比"提取标题和描述"多得多的事情:自动检测编码防止中文乱码、统计H1数量(超过1个就是SEO问题)、提取所有H2做结构分析、统计内外链比例、找出没有alt的图片、提取JSON-LD结构化数据类型。一次性输出成CSV,可以直接在Excel里做数据透视分析。
这个脚本的实际用途:拿到竞品200个页面的CSV后,你可以快速看出竞品标题的平均长度、是否统一用了品牌词后缀、H1和标题的关系(是重复还是互补)、描述的平均长度和模板规律、有没有做结构化数据、内链密度是否健康。这些信息用F12手动看基本不可能系统性分析出来。
三、JS渲染页面:用Playwright搞定requests拿不到的内容
现在越来越多的网站用了前端框架(Vue/React/Next.js),页面的正文内容是通过JS异步加载的,用requests拿到的HTML里只有一个空的根节点和一堆script标签。这种情况下,必须用浏览器自动化工具模拟真实浏览器渲染,等JS执行完了再提取DOM内容。
Playwright是目前这个场景的最佳选择,比Selenium更快更轻量,原生支持异步并发,而且可以设置等待条件(等某个元素加载完再开始提取)。
from playwright.sync_api import sync_playwrightdef extract_js_page(url):"""抓取JS渲染页面的SEO信息"""with sync_playwright() as p:# 启动无头浏览器browser = p.chromium.launch(headless=True)page = browser.new_page()# 设置User-Agent和视口page.set_viewport_size({'width': 1920, 'height': 1080})try:page.goto(url, wait_until='networkidle', timeout=30000)# 等待body内容加载完成(防止白屏)page.wait_for_selector('body', timeout=10000)info = {'url': url,'title': page.title(),'meta_description': '','h1': '','h1_count': 0,'word_count': 0,}# 获取meta descriptionmeta_desc = page.query_selector('meta[name="description"]')if meta_desc:info['meta_description'] = meta_desc.get_attribute('content') or ''# 获取H1h1_elements = page.query_selector_all('h1')info['h1_count'] = len(h1_elements)if h1_elements:info['h1'] = h1_elements[0].inner_text()# 获取正文纯文本字数body_text = page.inner_text('body')info['word_count'] = len(body_text.strip())# 获取渲染后的完整HTML(方便后续用BeautifulSoup二次解析)info['rendered_html'] = page.content()# 获取所有链接links = page.query_selector_all('a[href]')info['link_count'] = len(links)# 截图留档(可选)# page.screenshot(path=f'screenshot_{url_hash}.png', full_page=True)return infofinally:browser.close()# 批量抓取JS页面def batch_extract_js(urls):results = []for i, url in enumerate(urls):print(f'({i+1}/{len(urls)}) {url}')try:info = extract_js_page(url)results.append(info)except Exception as e:print(f' 失败: {e}')return resultsPlaywright方案和requests方案的核心区别:Playwright每次抓取都要启动一个完整的浏览器实例,耗时大约是requests的10-20倍,内存占用也大得多。所以不要对所有页面都用Playwright——先用requests试一下能不能拿到正文内容,拿不到再切换Playwright。一个实用的判断方法是:在浏览器里打开页面,右键"查看网页源代码",如果源代码里有你想要的文字内容,就用requests;如果源代码里只有一个空的div容器,那就必须用Playwright。
requests能搞定的
WordPress站点、传统PHP/JSP页面、静态HTML页面、百度搜索结果页、任何"查看源代码"能看到完整内容的页面。速度快、资源省、一个IP能撑几百次请求。
必须上Playwright的
Vue/React/Next.js前端渲染页面、有反爬验证码的页面、需要登录态的页面、内容通过AJAX异步加载的页面。速度慢但能拿到requests拿不到的数据。
四、浏览器扩展和可视化工具,零代码方案的取舍
不是每个人都会Python。如果你只需要临时抓几十个页面的数据,或者团队里没有开发人员,浏览器扩展和可视化工具是更务实的选择。
Web Scraper(Chrome扩展)是最轻量的零代码方案。安装后在目标网页上点选要抓取的元素,设置翻页规则,就能自动抓取并导出CSV。它天然支持JS渲染(因为跑在浏览器里),能处理分页、下拉加载、点击翻页等常见场景。缺点是免费版有页面数量限制,大量抓取需要付费版。
八爪鱼采集器是功能更强的桌面端工具,拖拽配置就能搞定登录验证、验证码识别、定时采集、云采集等需求。适合不会写代码但需要大批量持续抓取的场景。代价是免费版导出数据有限制,高级功能需要付费,而且桌面端工具跑起来占用资源。
零代码工具的隐藏成本:配置一个复杂的抓取规则(比如需要登录、需要翻页、需要提取多个嵌套层级的数据),在可视化工具里拖拽配置的时间可能比写Python脚本还长。而且规则保存后换个电脑或分享给别人就很麻烦。如果抓取逻辑比较简单(比如只提取标题和描述),零代码工具确实快;如果逻辑复杂(需要数据清洗、多维度分析、定制输出格式),学一点Python长期来看更划算。

五、抓取后怎么用:四个SEO场景的完整数据分析思路
很多人把数据抓下来了就不知道该干嘛了。CSV往桌上一扔,看两眼觉得没啥规律就放弃了。实际上抓取只是第一步,分析才是产生价值的地方。
场景一:竞品标题策略分析
把竞品200个页面的标题全部抓到Excel,用数据透视看:标题平均长度是多少、有没有统一的品牌词后缀、修饰词("多少钱""哪家好""排名")的出现频率分布。这些规律就是你的标题优化方向。
场景二:全站SEO健康检查
抓取自己全站页面,筛选出:H1数量不等于1的页面、描述缺失或超长的页面、没有canonical的页面、图片没有alt的页面。一个下午就能完成全站几百个页面的SEO审计。
场景三:结构化数据覆盖率统计
抓取竞品全站的JSON-LD数据,看他们都用了哪些Schema类型(Article/Product/FAQ/LocalBusiness),哪些类型你的站还没做。结构化数据是做富摘要出图的硬条件,竞品有而你没有的,就是机会。
场景四:内容结构逆向工程
抓取竞品排名前10的页面,提取H2标题列表,横向对比它们的章节结构和内容组织逻辑。不同排名的页面在H2的数量、顺序、措辞上有什么差异,这些差异可能就是排名差距的原因。
这四个场景的共同点:单靠肉眼浏览网页做不了系统性分析,只有把数据提取到表格里才能做量化对比。这也是为什么即使最简单的requests+BeautifulSoup脚本,价值也远超手动F12——它把"定性感觉"变成了"定量数据"。
六、抓取中的三个翻车现场,提前避开
翻车一:编码没处理,中文全是乱码
不同网站的编码不一样(GBK、GB2312、UTF-8),直接用response.text可能拿到乱码。用resp.encoding = resp.apparent_encoding自动检测编码,或者在headers里明确指定Accept-Charset。输出CSV时用utf-8-sig编码,确保Excel打开不乱码。
翻车二:User-Agent没设,直接被403拒绝
Python requests的默认User-Agent是"python-requests/2.x",很多网站直接拒绝这种请求。至少设一个浏览器的UA,更稳妥的做法是维护一个UA列表随机轮换。另外有些站会检查Accept-Language头,加上zh-CN的配置更不容易被拦。
翻车三:请求太快被目标站封IP
连续高频请求是抓取被封的第一原因。每次请求之间sleep 1-3秒,模拟人类的浏览速度。如果页面量上千,配合代理IP轮换使用。不要试图通过减小sleep时间来加速——被封后重试的时间成本远超老老实实等的那几秒。
requests单次耗时
0.5-2秒

静态页面,含网络延迟
Playwright单次耗时
5-15秒
含浏览器启动和JS渲染
安全请求间隔
1-3秒
低于1秒容易被风控
200页requests耗时
10-15分钟
含1.5秒间隔
七、robots协议和请求频率,两个不能跳过的合规底线
抓取网页数据做SEO分析,本身是合法的信息收集行为。但合规边界在于怎么抓、抓多少、抓完之后怎么用。
robots协议必须遵守:抓取任何网站前,先检查目标站的robots.txt。如果目标路径在Disallow里,就不要抓。robots协议虽然没有法律强制力,但尊重它是最基本的行业准则。Python代码里可以用urllib.robotparser自动检查,或者在抓取前手动打开目标站/robots.txt看一眼。
请求频率要有底线:即使是robots允许的路径,也不能无限制地高频抓取。1-3秒的请求间隔是对目标站服务器最基本的尊重。大量并发请求不仅可能被封,也可能给对方服务器造成负担。抓取公开数据做SEO分析是正当需求,但手段要合规。
另外,抓取到的竞品数据用于自己的SEO分析和优化参考是没问题的,但如果把竞品的原创内容直接复制发布到自己的网站上,那就越界了——这已经超出了"分析"的范畴,进入了"抄袭"。抓取工具帮你更快地看到竞品在做什么,但内容还是要自己写。
八、最后说一句
页面抓取这件事,技术门槛其实很低——一个会写Python的人十分钟就能搭好requests+BeautifulSoup的提取脚本。真正的分水岭在于抓完之后会不会分析。同样是200个页面的TDK数据,有人看完只得出"竞品标题大概30个字"这种粗浅结论,有人能从中分析出竞品的长尾词覆盖策略、标题模板规律、不同栏目使用不同标题结构的差异化打法。
对于多站运营的场景,页面抓取的需求更系统化。UC建站系统的内容中台本身就内置了竞品页面分析能力,把竞品站点的TDK规律、内容结构、长尾词覆盖情况自动拆解成可对比的数据维度,省去了逐个站点手动写抓取脚本的重复劳动。但不管用什么工具,抓取只是手段,把数据变成自己的优化决策才是目的。不要沉迷于"我又抓了多少个页面",多想想"这些数据告诉了我什么"。

