爬取网页内容 转为对LLM友好的MarkDown格式
一、Crawl4AI安装
基本安装
pip install crawl4ai
这将安装核心 Crawl4AI 库以及必要的依赖项。目前还没有包含任何高级功能(如 transformer 或 PyTorch)。
初始设置和诊断
运行 Setup 命令
安装后,调用:
crawl4ai-setup
它有什么作用?- 安装或更新所需的 Playwright 浏览器(Chromium、Firefox 等) - 执行作系统级别的检查(例如,Linux 上缺少库) - 确认您的环境已准备好进行爬网
诊断
或者,您可以运行诊断来确认一切正常:
crawl4ai-doctor
此命令尝试: - 检查 Python 版本兼容性 - 验证 Playwright 安装 - 检查环境变量或库冲突
二、使用方法
视频教程:【🚀 吐血爆肝整理最强大生成 AI 知识库工具!Crawl4AI 核心教程|附核心代码】https://www.bilibili.***/video/BV1xTMGz1E3m?vd_source=7b1d5b62108146d6c57a160c3984dd92
github项目地址:https://github.***/unclecode/crawl4ai?tab=readme-ov-file
crawl4ai使用文档:https://docs.crawl4ai.***/
crawl4ai有三种解析方法:修剪内容过滤器(PruningContentFilter),BM25内容过滤器,LLM内容过滤器(LLMContentFilter)
由于LLM需要调用API有成本,且响应慢,这里优先介绍Pruning和BM25
经过测试BM25对中文效果不好,对英文内容搜索更好,所以推荐使用Pruning
1.Pruning(修剪)
修剪,自动分析内容重要度,只保留重要的内容
适合AI知识库构建
import asyncio
from crawl4ai import AsyncWebCrawler, BrowserConfig, CrawlerRunConfig, CacheMode
from crawl4ai.content_filter_strategy import PruningContentFilter
from crawl4ai.markdown_generation_strategy import DefaultMarkdownGenerator
"""
Pruning: 修剪,只保留重要的内容,自动分析重要度
在Crawl4AI中,Pruning是通过DefaultMarkdownGenerator类实现的。
options:
ignore_links: 是否在最终markdown中移除所有超链接
ignore_images: 是否在最终markdown中移除所有图片
"""
async def main():
browser_config = BrowserConfig(
headless=True, # 是否无头模式,True:不打卡浏览器
viewport_width=1280, # 视口宽度
viewport_height=720, # 视口高度
user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", # 用户代理
text_mode=True, # 文本模式,禁用图片加载
) # 浏览器配置
run_config = CrawlerRunConfig(
cache_mode=CacheMode.DISABLED, # 禁用缓存模式,获取最新内容
markdown_generator = DefaultMarkdownGenerator(
content_filter=PruningContentFilter(
# min_word_threshold = 10, # 丢掉少于10个单词的块,因为他们可能太短或者无用
threshold = 0.76, # 丢掉重要度低于0.76的块,越高过滤越严格
threshold_type = "fixed", # 重要度阈值类型,fixed:固定值,dynamic:相对值
# threshold_type = "dynamic"
),
options = {
"ignore_links": True, # 是否在最终markdown中移除所有超链接
"ignore_images": True, # 是否在最终markdown中移除所有图片
}
)
) # 爬虫运行配置
async with AsyncWebCrawler(config=browser_config) as crawler:
try:
result = await crawler.arun(
url="https://www.anthropic.***/news/agent-capabilities-api",
config=run_config
)
except Exception as e:
print(f"错误:{e}")
with open(f"2.2.2result-{len(result.markdown.fit_markdown)}.md", "w", encoding="utf-8") as f:
f.write(result.markdown.fit_markdown)
print(f"内容长度:{len(result.markdown.fit_markdown)}")
print(f"已保存在:{f.name}")
if __name__ == "__main__":
asyncio.run(main())
2.BM25
向量检索,根据用户问题生成向量,然后根据向量检索文章相似内容,并返回文章相似内容片段
适合AI+联网搜索
经过测试BM25对中文效果不好,对英文内容搜索更好,所以推荐使用Pruning
import asyncio
from crawl4ai import AsyncWebCrawler, BrowserConfig, CrawlerRunConfig, CacheMode
from crawl4ai.content_filter_strategy import BM25ContentFilter
from crawl4ai.markdown_generation_strategy import DefaultMarkdownGenerator
"""
bm25: 向量检索,根据内容生成向量,然后根据向量检索相似内容
"""
async def main():
browser_config = BrowserConfig(
headless=True, # 是否无头模式,True:不打卡浏览器
viewport_width=1280, # 视口宽度
viewport_height=720, # 视口高度
user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", # 用户代理
text_mode=True, # 文本模式,禁用图片加载
) # 浏览器配置
run_config = CrawlerRunConfig(
cache_mode=CacheMode.DISABLED, # 禁用缓存模式,获取最新内容
markdown_generator = DefaultMarkdownGenerator(
content_filter=BM25ContentFilter(
user_query="Anthropic API",
bm25_threshold=1.2, #数值越高块越少
),
options = {
"ignore_links": True, # 是否在最终markdown中移除所有超链接
"ignore_images": True, # 是否在最终markdown中移除所有图片
}
)
) # 爬虫运行配置
async with AsyncWebCrawler(config=browser_config) as crawler:
try:
result = await crawler.arun(
url="https://www.anthropic.***/news/agent-capabilities-api",
config=run_config
)
except Exception as e:
print(f"错误:{e}")
with open(f"2.2.3result-{len(result.markdown.fit_markdown)}.md", "w", encoding="utf-8") as f:
f.write(result.markdown.fit_markdown)
print(f"内容长度:{len(result.markdown.fit_markdown)}")
print(f"已保存在:{f.name}")
if __name__ == "__main__":
asyncio.run(main())
3.并发url请求爬取(arun_many)
import asyncio
import os
import re
from crawl4ai import AsyncWebCrawler, BrowserConfig, CrawlerRunConfig, CacheMode
from crawl4ai.content_filter_strategy import BM25ContentFilter
from crawl4ai.markdown_generation_strategy import DefaultMarkdownGenerator
"""
bm25: 向量检索,根据内容生成向量,然后根据向量检索相似内容
"""
async def main():
browser_config = BrowserConfig(
headless=True, # 是否无头模式,True:不打卡浏览器
viewport_width=1280, # 视口宽度
viewport_height=720, # 视口高度
user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", # 用户代理
text_mode=True, # 文本模式,禁用图片加载
) # 浏览器配置
run_config = CrawlerRunConfig(
cache_mode=CacheMode.DISABLED, # 禁用缓存模式,获取最新内容
markdown_generator = DefaultMarkdownGenerator(
content_filter=BM25ContentFilter(
user_query="Anthropic API",
bm25_threshold=1.2,
),
options = {
"ignore_links": True, # 是否在最终markdown中移除所有超链接
"ignore_images": True, # 是否在最终markdown中移除所有图片
}
)
) # 爬虫运行配置
async with AsyncWebCrawler(config=browser_config) as crawler:
try:
results = await crawler.arun_many(
urls=[
"https://www.anthropic.***/news/agent-capabilities-api",
"https://www.anthropic.***/news/claude-4",
"https://www.anthropic.***/news/the-anthropic-economic-index"
],
config=run_config
)
except Exception as e:
print(f"错误:{e}")
return
# 创建输出目录(如果不存在)
output_dir = "anthropic_articles"
if not os.path.exists(output_dir):
os.makedirs(output_dir)
for result in results:
# 从URL生成文件名
filename = re.sub(r'https?://(?:www\.)?anthropic\.***/news/', '', result.url)
filename = re.sub(r'[^\w\-]', '_', filename) # 替换非字母数字字符为下划线
filepath = os.path.join(output_dir, f"{filename}-{len(result.markdown.fit_markdown)}.md")
# 保存到markdown文件
with open(filepath, 'w', encoding='utf-8') as f:
f.write(result.markdown.fit_markdown)
print(f"已保存: {filepath}")
if __name__ == "__main__":
asyncio.run(main())
三、配合serp api 搜索进行爬虫
1.定义search.py方法
import requests
import json
api_key = "xxxxx申请的key"
def serp_search(query, num_results=10, country="us", language="en", exclude_images=False, exclude_videos=False):
"""
使用SERP API执行搜索,并返回结果URL列表
参数:
query (str): 搜索查询词
num_results (int): 希望返回的结果数量
country (str): 搜索结果的国家代码,默认为'us'
language (str): 搜索结果的语言代码,默认为'en'
exclude_images (bool): 是否排除图片结果,默认为False
exclude_videos (bool): 是否排除视频结果,默认为False
返回:
list: 搜索结果URL列表
"""
url = "https://serpapi.***/search"
params = {
"api_key": api_key,
"q": query,
"num": num_results,
"gl": country,
"hl": language,
"engine": "google" # 使用Google搜索引擎
}
# 如果需要排除图片和视频,添加过滤器
if exclude_images or exclude_videos:
filter_terms = []
if exclude_images:
filter_terms.append("-filetype:jpg -filetype:png -filetype:gif -filetype:webp -filetype:jpeg")
if exclude_videos:
filter_terms.append("-filetype:mp4 -filetype:avi -filetype:mov -filetype:wmv -filetype:flv -site:youtube.*** -site:youtu.be -site:vimeo.*** -site:bilibili.*** -site:tiktok.***")
# 将过滤器添加到查询中
if filter_terms:
params["q"] = f"{query} {' '.join(filter_terms)}"
try:
response = requests.get(url, params=params)
response.raise_for_status()
search_results = response.json()
# 视频网站域名列表
video_domains = [
'youtube.***', 'youtu.be', 'vimeo.***', 'bilibili.***', 'b23.tv',
'tiktok.***', 'douyin.***', 'facebook.***/watch', 'facebook.***/videos',
'instagram.***/tv', 'instagram.***/reel', 'twitch.tv', 'huya.***',
'douyu.***', 'iqiyi.***', 'youku.***', 'v.qq.***', 'tudou.***','zhihu.***'
]
urls = []
if "organic_results" in search_results:
for result in search_results["organic_results"]:
# 跳过图片和视频结果
result_type = result.get("type", "").lower()
if (exclude_images and "image" in result_type) or (exclude_videos and "video" in result_type):
continue
if "link" in result:
link = result["link"]
# 如果需要排除视频,检查URL是否包含视频网站域名
if exclude_videos:
is_video_site = any(domain in link.lower() for domain in video_domains)
if is_video_site:
continue
urls.append(link)
if len(urls) >= num_results:
break
print(urls)
return urls
except requests.exceptions.RequestException as e:
print(f"SERP API请求出错: {e}")
return []
except (KeyError, json.JSONDecodeError) as e:
print(f"解析SERP API结果出错: {e}")
return []
# 使用示例
if __name__ == "__main__":
search_query = "王者荣耀最好玩的组合"
# SERP API搜索示例
results = serp_search(search_query, 10, country="***", language="zh-***", exclude_images=True, exclude_videos=True)
print(f"SERP API搜索 '{search_query}' 的结果:")
for i, url in enumerate(results, 1):
print(f"{i}. {url}")
2.使用crawl4ai
from search import serp_search
query = "王者荣耀最好玩的双人组合"
urls = serp_search(query, num_results=10, country="***", language="zh-***", exclude_images=True, exclude_videos=True)
import asyncio
import os
import re
from crawl4ai import AsyncWebCrawler, BrowserConfig, CrawlerRunConfig, CacheMode
from crawl4ai.content_filter_strategy import BM25ContentFilter, PruningContentFilter
from crawl4ai.markdown_generation_strategy import DefaultMarkdownGenerator
"""
这里使用PruningContentFilter,根据内容生成重要度,然后根据重要度过滤掉不重要的内容
经过测试BM25对中文效果不好,所以使用PruningContentFilter
"""
async def main(query,urls):
browser_config = BrowserConfig(
headless=True, # 是否无头模式,True:不打卡浏览器
viewport_width=1280, # 视口宽度
viewport_height=720, # 视口高度
user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", # 用户代理
text_mode=True, # 文本模式,禁用图片加载
) # 浏览器配置
run_config = CrawlerRunConfig(
cache_mode=CacheMode.DISABLED, # 禁用缓存模式,获取最新内容
markdown_generator = DefaultMarkdownGenerator(
content_filter=PruningContentFilter(
# min_word_threshold = 10, # 丢掉少于10个单词的块,因为他们可能太短或者无用
threshold = 0.7, # 丢掉重要度低于0.76的块,越高过滤越严格
threshold_type = "dynamic", # 重要度阈值类型,fixed:固定值,dynamic:相对值
# threshold_type = "fixed"
),
options = {
"ignore_links": True, # 是否在最终markdown中移除所有超链接
"ignore_images": True, # 是否在最终markdown中移除所有图片
}
)
) # 爬虫运行配置
async with AsyncWebCrawler(config=browser_config) as crawler:
try:
results = await crawler.arun_many(
urls=urls,
config=run_config
)
except Exception as e:
print(f"错误:{e}")
return
# 创建输出目录(如果不存在)
output_dir = "results"
if not os.path.exists(output_dir):
os.makedirs(output_dir)
for result in results:
# 检查爬取是否成功
if result.markdown is None or result.markdown.fit_markdown is None:
print(f"跳过失败的URL: {result.url}")
continue
# 从URL生成文件名
filename = re.sub(r'https?://(?:www\.)?anthropic\.***/news/', '', result.url)
filename = re.sub(r'[^\w\-]', '_', filename) # 替换非字母数字字符为下划线
filepath = os.path.join(output_dir, f"{filename}-{len(result.markdown.fit_markdown)}.md")
# 保存到markdown文件
with open(filepath, 'w', encoding='utf-8') as f:
f.write(result.markdown.fit_markdown)
print(f"已保存: {filepath}")
if __name__ == "__main__":
asyncio.run(main(query,urls))