修复页面抓取功能,使用 agent-browser 替代 requests
- 改用浏览器方式抓取页面内容,绑过反爬虫机制 - 从 accessibility tree snapshot 中提取文本内容 - 增加等待时间让页面完全加载 - 可抓取知乎等有反爬措施的网站
This commit is contained in:
+48
-20
@@ -155,43 +155,71 @@ class SearchService:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
def fetch_url_content(self, url):
|
def fetch_url_content(self, url):
|
||||||
"""抓取网页内容"""
|
"""抓取网页内容(使用 agent-browser 浏览器方式,绑过反爬虫)"""
|
||||||
try:
|
try:
|
||||||
headers = {
|
# 使用浏览器方式抓取
|
||||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
|
stdout, stderr, code = self._run_browser('open', url, '--timeout', '20000')
|
||||||
}
|
if code != 0:
|
||||||
response = requests.get(url, headers=headers, timeout=self.timeout)
|
print(f"打开页面失败: {stderr}")
|
||||||
response.raise_for_status()
|
return None
|
||||||
|
|
||||||
soup = BeautifulSoup(response.text, 'lxml')
|
# 等待页面加载
|
||||||
|
self._run_browser('wait', '5000')
|
||||||
|
|
||||||
# 提取标题
|
# 获取页面标题
|
||||||
title = soup.find('title')
|
stdout, stderr, code = self._run_browser('get', 'title', '--timeout', '5000')
|
||||||
title = title.text.strip() if title else ''
|
title = stdout.strip().replace('[agent-browser] ', '').strip() if code == 0 else ''
|
||||||
|
|
||||||
# 提取正文(简单提取,可优化)
|
# 获取页面内容(通过 snapshot 获取 accessibility tree)
|
||||||
# 移除脚本和样式
|
stdout, stderr, code = self._run_browser('snapshot', '--json', '--timeout', '15000')
|
||||||
for script in soup(['script', 'style']):
|
text = ''
|
||||||
script.decompose()
|
if code == 0 and stdout:
|
||||||
|
try:
|
||||||
|
data = json.loads(stdout)
|
||||||
|
snapshot = data.get('data', {}).get('snapshot', '')
|
||||||
|
# 从 snapshot 中提取所有 StaticText
|
||||||
|
text = self._extract_text_from_snapshot(snapshot)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
# 提取文本
|
# 获取 URL(可能被重定向)
|
||||||
text = soup.get_text(separator='\n', strip=True)
|
stdout, stderr, code = self._run_browser('get', 'url', '--timeout', '5000')
|
||||||
|
actual_url = stdout.strip() if code == 0 else url
|
||||||
|
|
||||||
# 提取元数据
|
# 关闭浏览器
|
||||||
meta_desc = soup.find('meta', attrs={'name': 'description'})
|
self._run_browser('close')
|
||||||
description = meta_desc['content'] if meta_desc else ''
|
|
||||||
|
# 提取描述(从页面内容的前200字符)
|
||||||
|
description = text[:200].strip() if text else ''
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'title': title,
|
'title': title,
|
||||||
'description': description,
|
'description': description,
|
||||||
'content': text,
|
'content': text,
|
||||||
'url': url,
|
'url': actual_url,
|
||||||
'fetch_date': datetime.now().isoformat()
|
'fetch_date': datetime.now().isoformat()
|
||||||
}
|
}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"抓取URL失败: {url}, 错误: {str(e)}")
|
print(f"抓取URL失败: {url}, 错误: {str(e)}")
|
||||||
|
# 尝试关闭浏览器
|
||||||
|
try:
|
||||||
|
self._run_browser('close')
|
||||||
|
except:
|
||||||
|
pass
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def _extract_text_from_snapshot(self, snapshot):
|
||||||
|
"""从 accessibility tree snapshot 中提取文本内容"""
|
||||||
|
# 提取所有 StaticText 行
|
||||||
|
texts = []
|
||||||
|
for line in snapshot.split('\n'):
|
||||||
|
if 'StaticText' in line:
|
||||||
|
# 格式: - StaticText "文本内容"
|
||||||
|
match = re.search(r'StaticText "([^"]+)"', line)
|
||||||
|
if match:
|
||||||
|
texts.append(match.group(1))
|
||||||
|
return '\n'.join(texts)
|
||||||
|
|
||||||
def search_articles(self, keyword, category=None):
|
def search_articles(self, keyword, category=None):
|
||||||
"""从内容库搜索"""
|
"""从内容库搜索"""
|
||||||
return db.search_articles(keyword, category)
|
return db.search_articles(keyword, category)
|
||||||
|
|||||||
Reference in New Issue
Block a user