把一个网页变成干净、结构化的数据是两项工作,不是一项。首先你得拿到页面,这听起来微不足道,直到目标网站给你一个CAPTCHA或一个空壳。然后你得读懂标记并提取出你真正想要的字段,而这一部分传统上意味着为每个站点手写脆弱的解析器。本指南把两个工具配成一对,各自处理其中一半:Crawlbase Crawling API负责采集页面,Perplexity AI负责把它解读成JSON。
这个构建是一个小巧、可运行的Python脚本,用于perplexity ai web scraping python:通过Crawling API获取HTML,把它裁剪到要紧的部分,转换成Markdown以节省token,再带着一段明确说明要提取什么的提示词把它交给Perplexity的API。需要拎清的要紧之处是分工。在这个流程里Perplexity并不爬取站点,它读取的是你给它的文本。获取、渲染和绕过封锁全都发生在Crawling API那一步。
为什么用Perplexity AI做网络抓取
经典的Python抓取倚靠requests和BeautifulSoup:你获取HTML,然后写选择器在DOM上一路走到你想要的字段。在整洁、稳定的页面上这工作得很好。当标记嵌套很深、各列表之间不一致,或每隔几周就被重写时,它就崩了,因为每一次变更都意味着重写选择器。
像Perplexity这样的LLM改变了这道等式的后半部分。你不再告诉它价格在DOM的哪里,而是告诉它你想要什么("产品标题、价格,以及一句话摘要"),它会像人那样读内容。它擅长从杂乱的文本里抽出结构并以JSON返回,那恰恰是你想要喂给管道的形状。这总体上和AI数据提取背后的理念相同,而Perplexity的Sonar模型在此之上还加了网络检索。
Perplexity在这里做什么、不做什么
Perplexity的API与OpenAI兼容,所以你用同一个openai客户端和一个不同的base URL和它对话。在这个抓取器里它只扮演一个角色:读取我们采集到的页面文本并返回结构化字段。它不是你的爬虫,它不在绕过封锁,它也不是你的代理。把那条边界保持清晰,架构就保持简单:Crawlbase拿到字节,Perplexity把它们理出意义。
搭建你的Python环境
你需要Python 3.8或更新版本。创建一个虚拟环境,让这个项目的依赖保持隔离,然后激活它。
python -m venv perplexity_env # Windows perplexity_env\Scripts\activate # macOS / Linux source perplexity_env/bin/activate
现在安装脚本所用的四个库。
pip install crawlbase beautifulsoup4 markdownify openai
- crawlbaseCrawling API的客户端,它获取并渲染页面。
- beautifulsoup4在你为之花费token之前,把HTML裁剪到相关的部分。
- markdownify把那个部分转换成Markdown,让模型拿到干净的文本,而不是一锅标签汤。
- openaiPerplexity的API所讲的那种与OpenAI兼容的客户端。
你还需要两把密钥。注册之后从仪表盘拿到一个Crawlbase令牌,并从你的Perplexity账户设置里拿到一把Perplexity API密钥。把两者都放在源码控制之外,存进环境变量或一个密钥文件,永远不要把它们粘进共享的代码里。
Crawlbase发放两种令牌。普通令牌返回静态HTML;JavaScript(JS)令牌会先在一个真实浏览器里渲染页面。如果你的目标在客户端构建它的内容(大多数现代商店和仪表盘都是如此),就用JS令牌,否则页面回来就是一个空壳。对一个服务端渲染的页面,普通令牌更快也更便宜。
第1步:用Crawling API获取页面
这是采集步骤,也是封锁被处理掉的地方。你把一个URL发给Crawling API;它把请求经由轮换的住宅IP路由,按需渲染JavaScript,应对那些会拦住一个普通requests.get的CAPTCHA和质询,并返回处理好的HTML。你自己从不需要碰一个代理池或一个无头浏览器。
把这个保存为crawl.py。我们用一个Amazon产品页作为示例目标。
from crawlbase import CrawlingAPI api = CrawlingAPI({'token': 'YOUR_CRAWLBASE_JS_TOKEN'}) def crawl(url: str) -> str: response = api.get(url, {'ajax_wait': 'true', 'page_wait': 3000}) if response['status_code'] != 200: raise RuntimeError(f'Crawl failed: {response["status_code"]}') return response['body'].decode('utf-8') if __name__ == '__main__': url = 'https://www.amazon.com/Art-War-DELUXE-Sun-Tzu/dp/9388369696' html = crawl(url) with open('output.html', 'w', encoding='utf-8') as f: f.write(html) print('Saved output.html')
用python crawl.py运行它。你会得到一个含有真实产品标记的output.html,而不是直接请求经常返回的那种被封锁或空白的页面。ajax_wait和page_wait选项告诉渲染器去等待异步内容;如果结果回来得很单薄,就把page_wait调高。这就是在这里使用Crawling API的全部意义:它是那个一开始就替你拿到一个可用页面的层,而那也正是让AI那一步成为可能的东西。
只有在你能拿到页面的前提下,AI那一步才管用。Crawling API接收一个令牌,在你需要时在一个真实浏览器里渲染页面,在服务端轮换住宅IP,并返回处理好的HTML,这样你就跳过了自己运行一个代理池和一支无头集群。先在免费层把它指向一个公开页面,再把结果直接喂给Perplexity。
第2步:裁剪HTML并把它转换成Markdown
一个完整的产品页是数百KB的导航栏、脚本和页脚。把那一切都发给Perplexity既慢又浪费,因为LLM定价按token计,而那些token大多是噪声。两个低成本步骤就能修好它:用BeautifulSoup只抓取你关心的那一部分,再把那一部分转换成Markdown,让模型读到的是干净的散文而不是标签。
把这个保存为parse.py。
from bs4 import BeautifulSoup from markdownify import markdownify as md def html_to_markdown(html: str) -> str: soup = BeautifulSoup(html, 'html.parser') element = soup.find(id='centerCol') or soup.body if element is None: raise ValueError('Could not find content section in HTML') return md(str(element))
centerCol这个id是这个Amazon页面上的主产品列;对一个不同的站点,在你浏览器的开发者工具里检查实时页面,并瞄准承载你想要字段的那个容器。or soup.body这个回退能让脚本在那个id缺失时不至于崩溃。选择器会随时间漂移,所以把目标容器当作你会回头复查的东西,而不是一份永久契约。
第3步:写提取提示词
提示词是你告诉Perplexity要提取什么、以及如何把它塑形的地方。对字段要具体,并要求只返回JSON,这样响应在下游就易于解析。一条system消息设定角色;user消息携带指令外加Markdown。
def build_prompt(markdown: str) -> list: return [ { 'role': 'system', 'content': 'You extract structured data from product pages. Reply with JSON only, no prose.', }, { 'role': 'user', 'content': ( 'Extract these fields from the Markdown:\n' '- title\n' '- price\n' '- rating\n' '- one_sentence_summary\n\n' f'Markdown:\n{markdown}\n\n' 'Respond with a single JSON object.' ), }, ]
清晰的字段名和一句明确的"只要JSON"指令就完成了大部分工作。如果你需要一个严格的形状,把你预期的每一个键都点名,模型就会紧紧照办。
第4步:调用Perplexity并组装抓取器
现在把它接起来。openai客户端指向https://api.perplexity.ai,你把提示词发给一个Sonar模型,并解析它返回的JSON。把这个保存为scraper.py。
import json from openai import OpenAI from crawl import crawl from parse import html_to_markdown from build_prompt import build_prompt URL = 'https://www.amazon.com/Art-War-DELUXE-Sun-Tzu/dp/9388369696' client = OpenAI( api_key='YOUR_PERPLEXITY_API_KEY', base_url='https://api.perplexity.ai', ) def scrape(url: str) -> dict: html = crawl(url) markdown = html_to_markdown(html) messages = build_prompt(markdown) response = client.chat.completions.create( model='sonar-pro', messages=messages, ) content = response.choices[0].message.content return json.loads(content) if __name__ == '__main__': data = scrape(URL) print(json.dumps(data, indent=2))
填进你的两把密钥之后,用python scraper.py运行它。sonar-pro模型是Perplexity更强的Sonar层级;sonar更便宜,对简单的提取也够用。两者都讲OpenAI的chat-completions格式,所以换模型是一行的改动。
输出长什么样
结果是一个干净的JSON对象,你可以把它写进数据库、一个CSV,或管道的下一阶段。
{ "title": "The Art of War (Deluxe Hardbound Edition)", "price": "$15.80", "rating": "4.7 out of 5", "one_sentence_summary": "An ancient Chinese treatise by Sun Tzu on strategy, planning, and adapting tactics to win conflicts." }
注意一个在生产中值得处理的小怪癖:Perplexity的Sonar模型是网络检索的,有时会在文本后追加像[1]这样的引用标记,或把JSON包在一个代码围栏里。如果json.loads抛错,就在解析前剥掉开头和结尾的围栏,或更坚决地指示模型返回原始JSON。一个简短的清理步骤就能让解析器满意。
需要记在心里的挑战与限制
把一个LLM和一个爬虫配成一对很强大,但它并非没有权衡。成本随token而扩展,所以随着你扩大规模,裁剪到Markdown那一步会越发要紧;别发送整页。延迟比一个手写的解析器更高,因为你每页都在等一个模型往返,这对数百页来说没问题,但对数百万页就值得重新考量。而且LLM偶尔会给某个字段贴错标签或凭空捏造一个值,所以要校验关键字段(把价格转成数字、检查必需的键),而不是盲目相信输出。
对于非常高量、固定schema、你已经知道确切字段的任务,一个确定性的解析器,或者Crawlbase自家的Crawling API及其现成的解析器,可能比一个LLM更便宜也更快。当页面多样、杂乱或经常变化时,AI这条路才挣得回它的开销。如果你想为这类工作比较各家AI供应商,借助Gemini AI进行网络抓取用一个不同的模型走了同样的模式。
让采集步骤保持不被封锁
上面的一切都假设第1步真的返回了一个真实页面。在难啃的商业目标上,那个假设正是抓取器失败的地方,所以一些习惯能让采集层保持健康。
- 当页面是客户端渲染时使用JS令牌。在那些站点上普通令牌返回的是渲染前的外壳,那时就没什么可供Perplexity读的了。
- 依靠轮换。Crawling API和Smart AI Proxy把请求经由轮换的住宅IP路由,这样就没有任何单一地址会触发速率限制。如果你自己搭建技术栈,这就是需要做对的部分。
- 给你的请求把握节奏并读懂状态码。把请求摊开,变换参数,并把质询率的上升当作一个收手的信号,而不是更使劲地推。
关于这方面的完整攻略,参见如何抓取网站而不被封锁。简短版本是:让Crawling API掌管采集和绕过封锁,让Perplexity掌管解读。把这两项职责分开,系统就易于推理。
核心要点
- 两个工具,两项工作。Crawling API在一个受信任的IP背后采集并渲染页面;Perplexity读取结果并返回结构化的JSON。Perplexity不是你的爬虫。
- 在你提示之前先裁剪。用BeautifulSoup抓取相关部分,用Markdown把它弄干净,这样你就把token花在内容上,而不是导航栏和脚本上。
-
Perplexity讲OpenAI。使用
openai客户端,base URL为https://api.perplexity.ai,并搭配一个像sonar-pro这样的Sonar模型。 - 提示只要JSON。明确点名字段并校验关键字段;Sonar可能追加引用标记或围栏,所以加上一个小的清理步骤。
- 绕过封锁活在第1步。对客户端渲染的页面用JS令牌,依靠轮换,并把握请求节奏,好让页面起码能回来。
常见问题
Perplexity AI会自己抓取网站吗?
不会,在这个工作流里不会。Perplexity读取你给它的文本并返回结构化数据;它不获取目标页面、不渲染JavaScript、也不处理封锁。Crawling API完成所有采集,包括轮换住宅IP和渲染,然后你把它的HTML或Markdown交给Perplexity去解读。把那条边界保持清晰,是理解这套架构的关键。
为什么要在把HTML发给Perplexity之前先转换成Markdown?
两个原因:成本和质量。一个完整的HTML页面大多是浪费token的导航、脚本和样式,而LLM定价按token计。用BeautifulSoup裁剪到相关部分并转换成Markdown,给模型干净的散文去读,这既降低了成本,又改善了提取准确度,因为要趟过的噪声更少了。
我该用哪个Perplexity模型,sonar还是sonar-pro?
对简单、结构良好的页面,从sonar起步;它更便宜,通常也足够准确。当提取质量要紧,或内容密集而多样时,转向sonar-pro。两者都用与OpenAI兼容的chat-completions格式,所以切换是对model参数的一行改动。
我需要普通的Crawlbase令牌还是JS令牌?
这取决于目标。对HTML里已经含有数据的服务端渲染页面,用普通令牌。当站点在客户端构建它的内容时用JS令牌,大多数现代商店和应用都是如此,因为普通令牌会返回那个空白的渲染前外壳,让Perplexity无从提取。
JSON解析一直失败。哪里出问题了?
Perplexity的Sonar模型是网络检索的,可能把输出包在一个代码围栏里,或追加像[1]这样的引用标记,这会让json.loads崩掉。在解析前剥掉任何开头和结尾的围栏,把提示词收紧到坚持只要原始JSON、不带额外文本,并在使用解析后的对象之前校验它。一个小的清理步骤能让管道可靠。
我能把Perplexity AI和Crawlbase一起大规模使用吗?
能,而且它们彼此互补得很好。Crawlbase处理采集和绕过封锁,让你的请求持续落地,而Perplexity把每一页变成结构化数据。对于固定schema的非常高量,把每页的LLM成本与一个确定性解析器或Scraper API权衡一下;当页面杂乱或经常变化时AI这条路大放异彩,而固定schema的任务不用LLM可能更便宜。
大规模爬取任何站点,无需与基础设施对抗。
Crawlbase 负责处理代理、指纹和 CAPTCHA,让你的团队专注于交付数据流水线,而非维护爬取管道。1,000 次请求免费,无需信用卡。
