手里有500个竞品URL,想批量提取每个页面的title、meta description、H1标签和完整HTML源码,每个页面打开F12、右键查看源代码、Ctrl+S另存为——500个页面做完要两天而且中间一定会手抖漏掉几个。换成wget一行命令10分钟跑完,但跑出来的200个页面全是乱码因为目标站用的是GBK编码而wget默认按UTF-8保存。换Python requests加了编码检测,结果有80个页面提取出来的HTML只有三行——<div id="app"></div>一个空壳,因为那是Vue/React渲染的SPA页面,requests拿不到JS执行后的DOM。再换Puppeteer无头浏览器,500个页面跑到第200个的时候内存爆了,因为每开一个页面Puppeteer就启动一个Chromium实例,200个实例把16GB内存吃光了。HTML源码批量提取这件事,到底是工具不对还是你没选对场景对应的工具
先问三个问题,再选工具
目标页面是静态HTML还是JS渲染的SPA? — 决定了用requests还是Puppeteer。
只要HTML源码还是要连带CSS/JS/图片整站扒下来? — 决定了用curl还是HTTrack。
批量规模是几十个URL还是几千几万个? — 决定了用Shell脚本还是Scrapy分布式。
十款工具按使用场景分类,不是按价格分类
HTML源码提取这件事,贵的工具不一定对,免费的不一定差。关键是你面对的是静态页面还是动态页面、要的是整站镜像还是单页源码、跑的是几十个URL还是几万个。
快速选型口诀:10个以内URL → 在线工具逐页查看;10-100个URL且静态页面 → wget+Shell脚本循环;100-1000个URL且可能含动态页面 → Python requests + Puppeteer混用;1000个以上 → Scrapy框架;需要整站镜像含CSS/JS/图片 → HTTrack;完全不想写代码 → Octoparse;要绕过Cloudflare/验证码 → ScrapingBee API。
wget和curl:看似简单,参数不对就翻车
wget是最被低估的批量源码提取工具。一行命令能做的事远超大部分人的认知。
wget 三个最常用的批量场景
# 场景1:从URL列表文件批量下载HTML源码

wget -i urls.txt -O- > all_sources.html
# -i 从文件读取URL列表,每行一个URL
# 场景2:递归镜像整个网站(含CSS/JS/图片)
wget -r -l 3 -p -k -np https://example.com/
# -r 递归 -l 3 深度3层 -p 下载页面所需资源 -k 转换链接为本地路径
# 场景3:批量下载并分别保存为独立文件
wget -i urls.txt --wait=1 --random-wait --limit-rate=200k
# --wait=1 间隔1秒 --random-wait 随机化间隔(反反爬) --limit-rate 限速
wget翻车的两个经典场景:第一,目标站返回的Content-Type是GBK/GB2312,但wget不做编码转换,保存到本地后用UTF-8编辑器打开全是乱码。解决方案是下载后用iconv -f GBK -t UTF-8 file.html转码,或者在Python脚本里用requests获取后用response.apparent_encoding自动检测。第二,递归下载时没限速也没设间隔,对着目标站每秒发几十个请求,10分钟后IP被封。wget的--wait和--limit-rate参数就是用来避免这种情况的,但很多人都不知道有这两个参数。
curl的单页提取比wget更轻量,但批量需要Shell脚本包装。一个典型的批量脚本:
while read url; do
filename=$(echo "$url" | md5sum | cut -d' ' -f1).html
curl -s -L -A "Mozilla/5.0" --connect-timeout 10 "$url" -o "output/$filename"

sleep 1
done < urls.txt
-s静默模式不显示进度条,-L跟随重定向,-A伪装User-Agent,--connect-timeout 10超时10秒就跳过不卡住,sleep 1每秒只发一个请求。
Puppeteer和Playwright:能拿SPA页面,但内存是硬伤
Vue/React/Angular构建的SPA页面,HTML源码只有一行<div id="app"></div>,所有内容都是JS在浏览器里渲染出来的。wget、curl、Python requests拿到这个空壳毫无意义。这时候需要无头浏览器——Puppeteer或Playwright。
Puppeteer — 单浏览器实例复用
核心技巧:启动一个Chromium实例,然后在这个实例里打开多个页面(page),而不是每爬一个URL启动一个新实例。一个Chromium实例约占300MB内存,但100个page共享同一个实例,总内存约500MB。如果每爬一个URL就puppeteer.launch()一次,100个URL就是100个Chromium实例 = 30GB内存。另外用page.waitForSelector()等目标元素出现再拿HTML,而不是固定setTimeout(5000)——快很多也稳很多。
Playwright — 多浏览器+自动等待
Playwright比Puppeteer多两个关键优势:①支持Chromium/Firefox/WebKit三个浏览器引擎,能覆盖更广的目标站兼容性;②自动等待元素可见——page.goto()默认等页面加载到networkidle状态,不需要手动写waitForSelector。缺点是对内存的控制不如Puppeteer精细。如果只需要爬Chromium,Puppeteer更轻;如果需要跨浏览器测试或爬WebKit专属站点,Playwright。
关于绕过Cloudflare和反爬:Puppeteer/Playwright默认启动的无头Chrome会被Cloudflare的JS挑战检测出来(navigator.webdriver=true、缺少常见浏览器插件等)。需要用puppeteer-extra + puppeteer-extra-plugin-stealth这个组合,它会自动修补Chrome的WebDriver标记、模拟真实浏览器的指纹特征。但如果目标站开了Cloudflare Turnstile(人机验证),Stealth插件也无能为力,这时候只能上ScrapingBee这类云端API——它们有住宅IP池和验证码自动处理。
Scrapy:万级URL的真正答案
当你的URL列表超过1000个时,Shell脚本+wget逐个下载就太慢了——单线程每秒一个请求,1000个URL要跑16分钟。Scrapy的异步引擎默认16个并发请求,同样的1000个URL约1分钟跑完。但Scrapy对新手不友好:需要理解Spider/Item/Pipeline/Middleware的概念,配置settings.py里的并发数、下载延迟、User-Agent轮换、代理中间件。
如果只是为了提取HTML源码而不是做数据解析,Scrapy有点杀鸡用牛刀。但如果你提取完源码之后还要解析里面的title/meta/H1/所有链接,Scrapy就是最合适的——它的CSS选择器和XPath提取能力是wget和curl不具备的。
六个翻车场景,全和工具选择有关
翻车一:用requests爬SPA拿到了200个空壳
一个SEO从业者想批量提取竞品的产品页HTML,分析人家的title和meta写法。Python requests跑完200个URL,打开保存的HTML文件一看,每个文件只有<div id="app">和一堆打包后的JS文件名——因为竞品网站是用Vue做的。教训:提取前先浏览器里打开一个目标URL,右键"查看网页源代码",如果源代码里能看到完整内容→用requests/wget;如果源代码几乎为空→用Puppeteer/Playwright。
翻车二:wget递归下载把整个站扒下来发现200GB
wget -r不加-l(深度限制)和-A(文件类型过滤)参数,会顺着每一个链接一直爬下去,连站外的链接都跟着跑。更可怕的是下载了所有PDF、ZIP、MP4等二进制文件。一个企业站爬了6个小时还没停,硬盘空间从50GB剩余变成0。正确做法:wget -r -l 2 -A "html,htm,css,js" --reject "pdf,zip,mp4"。
翻车三:GBK编码的站全部变成了乱码
中文站有大量还在用GBK/GB2312编码的老站点。wget和requests默认按服务器的Content-Type头来处理编码,但如果服务器没返回charset,或者HTTP头说UTF-8但meta标签说GBK,保存的文件就全乱码。Python解决方案:用chardet.detect(content)['encoding']自动检测实际编码再decode。Shell方案:iconv -f GBK -t UTF-8批量转码。
翻车四:Puppeteer跑到一半内存爆了
每爬一个URL就puppeteer.launch()一次,500个URL启动了500个Chromium实例,16GB内存全吃光。修复:一个浏览器实例+多个page复用,爬完一个page就close掉释放内存,爬500个URL只需要一个约500MB的浏览器实例。另外用--disable-dev-shm-usage和--disable-gpu启动参数减少内存占用。
翻车五:IP被封了还不知道,保存的文件全是Cloudflare 403页面
wget批量下载了200个URL,打开文件发现内容全都是Cloudflare的"Just a moment..."JS挑战页面或者403 Forbidden,一个有效HTML都没有。因为没检查HTTP状态码,脚本默默把错误页面当成功内容保存了。修复:每次请求后检查状态码是否为200,不是200就记录到错误日志;加上User-Agent伪装和请求间隔。
翻车六:提取完500个页面的源码不知道怎么用
花了半天时间把500个竞品页面的HTML源码全部提取到本地,然后看着500个.html文件傻眼了——怎么批量提取里面的title、meta description、H1、H2、所有外链?答案是再用一遍工具。轻量方案:Python BeautifulSoup + os.walk遍历文件目录,每个文件解析提取目标标签,写入CSV。重量方案:Scrapy直接配好Item Pipeline,采集和解析一步到位。如果一开始就用Scrapy,根本不需要"先下载HTML再解析"两步走。
七个场景的选型速查
HTML源码批量提取这件事,技术栈的选择比工具本身重要得多。用错工具不是多花时间的问题,而是你花了一天跑完发现拿到的全是乱码、空壳、或者403页面——等于白干。提取之前花30秒确认三件事:目标页是静态还是SPA、大概有多少个URL、提取完之后要不要解析里面的字段。这三个问题的答案直接决定了你该用wget一行命令、Python脚本、Scrapy框架还是ScrapingBee API。工具没有好坏,放对场景才是关键。
