用JavaScript抓网页,网页最直接的内容方式就是fetch。浏览器里敲几行代码,网页一个请求发出去,内容HTML文本就回来了。网页听起来挺简单,内容但实际跑起来你会发现,网页拿到的内容往往不是你想要的那部分内容。
先看个例子。网页假设我想抓某本书的内容豆瓣页面,拿它的网页评分。代码大概长这样:
fetch('https://book.douban.com/subject/xxx/') .then(res => res.text()) .then(html => { console.log(html); }); 打开控制台,内容你会看到一大坨HTML。网页但问题来了——那个评分数字藏在几千行标签里,内容你不可能用眼睛去找。网页这时候就得靠解析工具,DOMParser是浏览器自带的,不用装任何库:
const parser = new DOMParser(); const doc = parser.parseFromString(html, 'text/html'); const rating = doc.querySelector('strong.rating_num').textContent; console.log(rating); // 比如 "9.2" 到这一步,单页抓取基本就通了。但现实情况比这麻烦得多。
第一个坑是跨域。你在自己网站的页面里fetch别人的域名,浏览器会直接拦下来,报CORS错误。解决办法要么是让后端代理请求,要么用CORS代理服务,比如加个前缀。但这玩意儿不稳定,公共代理随时可能挂掉。我之前用过一个叫cors-anywhere的,刚开始挺好使,后来被滥用得厉害,现在基本要自己部署。
第二个坑是动态渲染。很多页面内容不是HTML里写死的,而是JavaScript执行后才填进去的。fetch拿到的只是初始HTML,里面可能只有一个空的div。你querySelector找半天,返回null。这种情况要么分析它的API接口直接拿数据,要么上Puppeteer这种无头浏览器。Puppeteer能等页面渲染完再抓,代价是慢,吃内存,服务器上跑几十个实例就顶不住了。
说个具体案例。有次帮朋友抓一个电商网站的价格,fetch拿到的HTML里价格区域是空的。打开Network面板一看,价格是通过一个XHR请求拿到的JSON。那还解析什么HTML,直接请求那个接口就行,返回的数据干净得多。所以抓之前先看Network,能省很多事。
第三个坑是反爬。频率高了IP被封,User-Agent不对被识别,有些站点还会检查Cookie和Referer。简单的应对是加请求头,控制并发,随机延迟。但道高一尺魔高一丈,复杂的反爬系统会分析鼠标轨迹、浏览器指纹,那就不是几行JS能解决的了。
还有个容易忽略的点:Node.js环境里没有DOMParser和fetch(Node 18之前没有原生fetch)。你得用node-fetch或者axios发请求,用cheerio解析HTML。cheerio的API跟jQuery很像,上手快,但它不执行JavaScript,纯静态解析。选哪个工具取决于目标页面是静态还是动态。
总结一下我的经验:静态页面用fetch加DOMParser(浏览器)或axios加cheerio(Node),动态页面先找API,找不到再上Puppeteer。跨域问题让后端扛,别在前端硬刚。频率控制好,别把人家服务器搞崩了,不然封你没商量。
抓网页这件事,工具不难,难的是每个网站都有自己的脾气。你得针对性地试,看Network,读源码,慢慢摸清楚它的数据是怎么来的。没有一招吃遍天的方案。