30 KiB
新闻爬虫系统 - 添加新爬虫实现指南
目录
项目架构概述
核心组件
crawler-module/
├── src/
│ ├── base/ # 基类层
│ │ ├── crawler_base.py # 爬虫基类
│ │ └── parser_base.py # 解析器基类
│ ├── crawlers/ # 爬虫实现层
│ │ ├── netease/ # 网易爬虫
│ │ ├── kr36/ # 36氪爬虫
│ │ └── sina/ # 新浪爬虫
│ ├── parsers/ # 解析器层
│ │ ├── netease_parser.py
│ │ ├── kr36_parser.py
│ │ └── sina_parser.py
│ ├── utils/ # 工具层
│ │ ├── http_client.py # HTTP客户端
│ │ ├── selenium_driver.py # Selenium驱动
│ │ └── logger.py # 日志工具
│ ├── database/ # 数据层
│ │ ├── models.py # 数据模型
│ │ ├── repository.py # 数据访问
│ │ └── connection.py # 数据库连接
│ └── cli/ # CLI入口
│ └── main.py # 命令行接口
└── config/
├── config.yaml # 配置文件
└── settings.py # 配置加载器
架构设计模式
- 基类继承模式: 所有爬虫继承
DynamicCrawler或StaticCrawler - 解析器分离模式: 爬虫负责抓取URL列表,解析器负责解析详情页
- 配置驱动模式: 通过 YAML 配置文件管理爬虫参数
- 工厂模式: CLI 通过动态导入创建爬虫实例
爬虫类型
| 类型 | 基类 | 适用场景 | 依赖 | 示例 |
|---|---|---|---|---|
| API爬虫 | StaticCrawler |
有数据API接口 | requests | 腾讯科技/财经 |
| 静态爬虫 | StaticCrawler |
HTML直接渲染 | requests | 简单网站 |
| 动态爬虫 | DynamicCrawler |
JS动态加载 | Selenium | 网易/36氪 |
新增功能(v2.0)
- 多爬虫运行: 支持同时运行多个指定爬虫
- 统计展示: 自动展示爬取数、插入数、重复数
- 分组统计: 按数据源分组展示汇总信息
添加新爬虫的完整流程
步骤概览
1. 分析目标网站
↓
2. 创建爬虫类文件
↓
3. 创建解析器类文件
↓
4. 更新配置文件
↓
5. 注册爬虫到CLI
↓
6. 测试和调试
详细实现步骤
步骤 1: 分析目标网站
在编写代码之前,需要分析目标网站的以下信息:
1.1 确定网站类型和爬虫方式
- API接口: 网站提供数据API(优先选择,性能最好)
- 静态网站: 内容直接在 HTML 中,使用
StaticCrawler - 动态网站: 内容通过 JavaScript 加载,使用
DynamicCrawler
1.2 确定关键信息
- 列表页 URL
- 文章 URL 提取规则(CSS 选择器)
- 文章详情页结构
- 标题、时间、作者、正文的选择器
1.3 确定分类信息
- 分类名称(如:科技、娱乐、财经)
- 分类 ID(需与数据库一致)
- 分类代码(如:tech, entertainment, finance)
步骤 2: 创建爬虫类文件
2.1 创建目录结构
假设要添加一个名为 example 的网站,分类为 tech:
# 创建网站目录
mkdir src/crawlers/example
# 创建 __init__.py
touch src/crawlers/example/__init__.py
2.2 编写爬虫类
创建文件 src/crawlers/example/tech.py:
"""
Example 科技新闻爬虫
"""
from typing import List
from bs4 import BeautifulSoup
import sys
import os
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
from base.crawler_base import DynamicCrawler, Article
from parsers.example_parser import ExampleParser
class TechCrawler(DynamicCrawler):
"""Example 科技新闻爬虫"""
def _extract_article_urls(self, html: str) -> List[str]:
"""
从HTML中提取文章URL列表
Args:
html: 页面HTML内容
Returns:
文章URL列表
"""
soup = BeautifulSoup(html, "lxml")
urls = []
# 根据实际网站结构编写选择器
news_items = soup.select("div.news-list div.news-item")
for item in news_items:
article_link = item.select_one("a.title")
if article_link:
href = article_link.get('href')
if href:
# 处理相对路径
if href.startswith('/'):
href = f"https://www.example.com{href}"
urls.append(href)
return urls
def _fetch_articles(self, urls: List[str]) -> List[Article]:
"""
爬取文章详情
Args:
urls: 文章URL列表
Returns:
文章列表
"""
articles = []
parser = ExampleParser()
for i, url in enumerate(urls[:self.max_articles]):
try:
article = parser.parse(url)
article.category_id = self.category_id
article.source = "Example"
if not article.author:
article.author = "Example科技"
if article.is_valid():
articles.append(article)
self.logger.info(f"[{i+1}/{len(urls)}] {article.title}")
except Exception as e:
self.logger.error(f"解析文章失败: {url} - {e}")
continue
return articles
2.3 爬虫类说明
继承基类选择:
DynamicCrawler: 使用 Selenium,适合动态网站(需滚动加载)StaticCrawler: 使用 requests,适合静态网站或API接口
三种实现方式:
-
API爬虫(推荐,性能最好)
- 继承
StaticCrawler - 重写
crawl()方法 - 直接调用API接口获取数据
- 参考:
src/crawlers/tencent/tech.py
- 继承
-
静态爬虫
- 继承
StaticCrawler - 实现
_extract_article_urls()和_fetch_articles() - 使用 BeautifulSoup 解析HTML
- 继承
-
动态爬虫
- 继承
DynamicCrawler - 实现
_extract_article_urls()和_fetch_articles() - 使用 Selenium 自动化浏览器
- 继承
必须实现的方法:
_extract_article_urls(html): 从列表页提取文章 URL_fetch_articles(urls): 爬取每篇文章的详情
可用的属性:
self.url: 列表页 URLself.category_id: 分类 IDself.category_name: 分类名称self.css_selector: 等待加载的 CSS 选择器self.max_articles: 最大文章数self.http_client: HTTP 客户端(StaticCrawler)self.driver: Selenium 驱动(DynamicCrawler)self.logger: 日志记录器
步骤 3: 创建解析器类文件
3.1 创建解析器文件
创建文件 src/parsers/example_parser.py:
"""
Example 文章解析器
"""
import re
from bs4 import BeautifulSoup
import sys
import os
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from base.parser_base import BaseParser
from base.crawler_base import Article
from utils.http_client import HttpClient
from utils.logger import get_logger
class ExampleParser(BaseParser):
"""Example 文章解析器"""
def __init__(self):
self.logger = get_logger(__name__)
self.http_client = HttpClient()
def parse(self, url: str) -> Article:
"""
解析文章详情页
Args:
url: 文章URL
Returns:
文章对象
"""
# 获取页面 HTML
html = self.http_client.get(url)
soup = BeautifulSoup(html, "lxml")
# 提取标题
title = None
title_tag = soup.select_one("h1.article-title")
if title_tag:
title = title_tag.get_text(strip=True)
# 提取发布时间
publish_time = None
time_tag = soup.select_one("div.article-info span.publish-time")
if time_tag:
time_text = time_tag.get_text(strip=True)
# 标准化时间格式
time_match = re.search(r"\d{4}-\d{2}-\d{2}", time_text)
if time_match:
publish_time = time_match.group()
# 提取作者
author = None
author_tag = soup.select_one("div.article-info span.author")
if author_tag:
author = author_tag.get_text(strip=True)
# 提取正文内容
content_lines = []
article_body = soup.select_one("div.article-content")
if article_body:
# 移除不需要的标签
for tag in article_body.select("script, style, iframe, .ad"):
tag.decompose()
# 提取段落
for p in article_body.find_all("p"):
text = p.get_text(strip=True)
if text:
content_lines.append(text)
content = '\n'.join(content_lines)
return Article(
url=url,
title=title,
publish_time=publish_time,
author=author,
content=content,
)
3.2 解析器类说明
必须实现的方法:
parse(url): 解析文章详情页,返回 Article 对象
Article 对象字段:
url: 文章 URL(必需)title: 文章标题(必需)content: 文章内容(必需)publish_time: 发布时间(可选)author: 作者(可选)category_id: 分类 ID(由爬虫设置)source: 新闻源(由爬虫设置)
可用的工具:
self.http_client: HTTP 客户端self.logger: 日志记录器
步骤 4: 更新配置文件
编辑 config/config.yaml,在 sources 节点下添加新网站配置:
sources:
# ... 其他网站配置 ...
example:
base_url: "https://www.example.com"
categories:
tech:
url: "https://www.example.com/tech"
category_id: 4
name: "科技"
css_selector: "div.news-list" # 列表页等待加载的选择器
# 可以添加更多分类
entertainment:
url: "https://www.example.com/entertainment"
category_id: 1
name: "娱乐"
css_selector: "div.news-list"
配置项说明
| 配置项 | 说明 | 示例 |
|---|---|---|
base_url |
网站基础 URL | https://www.example.com |
url |
列表页 URL | https://www.example.com/tech |
category_id |
分类 ID(需与数据库一致) | 4 |
name |
分类名称 | 科技 |
css_selector |
列表页等待加载的选择器 | div.news-list |
分类 ID 对照表
根据项目文档,分类 ID 如下:
| ID | 分类名称 | 代码 |
|---|---|---|
| 1 | 娱乐 | entertainment |
| 2 | 体育 | sports |
| 3 | 财经 | finance |
| 4 | 科技 | tech |
| 5 | 军事 | war |
| 6 | 汽车 | auto |
| 7 | 政务 | gov |
| 8 | 健康 | health |
| 9 | AI | ai |
| 10 | 教育 | education |
步骤 5: 注册爬虫到 CLI
编辑 src/cli/main.py,在 CRAWLER_CLASSES 字典中添加新爬虫:
CRAWLER_CLASSES = {
# ... 其他爬虫配置 ...
'example': {
'tech': ('crawlers.example.tech', 'TechCrawler'),
'entertainment': ('crawlers.example.entertainment', 'EntertainmentCrawler'),
# 可以添加更多分类
},
}
注册格式说明
'网站代码': {
'分类代码': ('爬虫模块路径', '爬虫类名'),
}
示例:
'example': 网站代码(对应配置文件中的 sources.example)'tech': 分类代码(对应配置文件中的 categories.tech)'crawlers.example.tech': 模块路径(相对于 src 目录)'TechCrawler': 爬虫类名
步骤 6: 测试和调试
6.1 运行爬虫
方式1:单个爬虫
python -m src.cli.main example:tech
python -m src.cli.main example:tech --max 3
方式2:多个爬虫(v2.0新增)
python -m src.cli.main example:tech example:finance netease:tech --max 5
方式3:所有爬虫
python -m src.cli.main --all
python -m src.cli.main --all --max 5
6.2 统计信息展示(v2.0新增)
单个爬虫输出:
============================================================
爬虫统计: example:tech
============================================================
状态: [成功]
爬取数量: 10 篇
插入数量: 8 条
重复数量: 2 条
============================================================
多个爬虫输出:
================================================================================
爬虫任务汇总统计
================================================================================
【EXAMPLE】
--------------------------------------------------------------------------------
分类 状态 爬取 插入 重复
--------------------------------------------------------------------------------
tech [成功] 10 8 2
finance [成功] 10 9 1
--------------------------------------------------------------------------------
小计 2/2 成功 20 17 3
【NETEASE】
--------------------------------------------------------------------------------
分类 状态 爬取 插入 重复
--------------------------------------------------------------------------------
tech [成功] 10 10 0
--------------------------------------------------------------------------------
小计 1/1 成功 10 10 0
================================================================================
总计统计
================================================================================
总爬虫数: 3
成功数: 3
失败数: 0
总爬取: 30 篇
总插入: 27 条
总重复: 3 条
================================================================================
6.3 列出所有爬虫
python -m src.cli.main --list
输出:
可用的爬虫:
- netease:entertainment
- netease:tech
- kr36:ai
- example:tech
- example:entertainment
6.4 查看日志
# 日志文件位置
type logs\crawler.log
# Linux/Mac:
tail -f logs/crawler.log
6.4 调试技巧
开启调试模式:
python -m src.cli.main example:tech --debug
手动测试解析器:
from parsers.example_parser import ExampleParser
parser = ExampleParser()
article = parser.parse("https://www.example.com/article/123")
print(article.title)
print(article.content)
手动测试爬虫:
from crawlers.example.tech import TechCrawler
crawler = TechCrawler('example', 'tech')
crawler.max_articles = 3
articles = crawler.crawl()
for article in articles:
print(article.title)
示例代码
示例1:API爬虫(腾讯财经)
适用场景: 网站提供数据API接口,性能最好
爬虫类: src/crawlers/tencent/finance.py
"""
腾讯财经新闻爬虫(API版)
"""
import time
import random
import hashlib
from typing import List
import requests
from base.crawler_base import StaticCrawler, Article
from parsers.tencent_parser import TencentParser
class FinanceCrawler(StaticCrawler):
"""腾讯财经新闻爬虫(API版)"""
def __init__(self, source: str, category: str):
super().__init__(source, category)
# 腾讯API配置
self.api_url = "https://i.news.qq.com/web_feed/getPCList"
self.channel_id = "news_news_finance" # 财经频道ID
self.seen_ids = set()
self.item_count = 20 # 每页固定请求20条
def crawl(self) -> List[Article]:
"""执行爬取任务(重写基类方法以支持API接口)"""
self.logger.info(f"开始爬取腾讯{self.category_name}新闻")
try:
device_id = self._generate_trace_id()
article_urls = self._fetch_article_urls_from_api(device_id)
self.logger.info(f"找到 {len(article_urls)} 篇文章")
articles = self._fetch_articles(article_urls)
self.logger.info(f"成功爬取 {len(articles)} 篇文章")
return articles
except Exception as e:
self.logger.error(f"爬取失败: {e}", exc_info=True)
return []
finally:
self._cleanup()
def _fetch_article_urls_from_api(self, device_id: str) -> List[str]:
"""从API获取文章URL列表"""
urls = []
import math
max_pages = math.ceil(self.max_articles / self.item_count)
for flush_num in range(max_pages):
payload = {
"base_req": {"from": "pc"},
"forward": "1",
"qimei36": device_id,
"device_id": device_id,
"flush_num": flush_num + 1,
"channel_id": self.channel_id,
"item_count": self.item_count,
"is_local_chlid": "0"
}
try:
response = requests.post(self.api_url, json=payload, timeout=10)
if response.status_code == 200:
data = response.json()
if data.get("code") == 0 and "data" in data:
news_list = data["data"]
for item in news_list:
news_id = item.get("id")
if news_id not in self.seen_ids:
self.seen_ids.add(news_id)
url = item.get("link_info", {}).get("url")
if url:
urls.append(url)
if len(urls) >= self.max_articles:
break
if len(urls) >= self.max_articles:
break
except Exception as e:
self.logger.error(f"获取API数据失败: {e}")
time.sleep(random.uniform(1, 2))
return urls
def _generate_trace_id(self):
"""生成trace_id"""
random_str = str(random.random()) + str(time.time())
return "0_" + hashlib.md5(random_str.encode()).hexdigest()[:12]
配置: config/config.yaml
tencent:
categories:
finance:
url: "https://news.qq.com/ch/finance"
category_id: 3
name: "财经"
css_selector: ""
运行:
python -m src.cli.main tencent:finance --max 5
示例2:动态爬虫(网易科技)
完整示例:添加新浪娱乐爬虫
假设我们要为新浪网站添加娱乐分类爬虫:
1. 创建爬虫类
文件:src/crawlers/sina/entertainment.py
"""
新浪娱乐新闻爬虫
"""
from typing import List
from bs4 import BeautifulSoup
import sys
import os
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
from base.crawler_base import DynamicCrawler, Article
from parsers.sina_parser import SinaEntertainmentParser
class EntertainmentCrawler(DynamicCrawler):
"""新浪娱乐新闻爬虫"""
def _extract_article_urls(self, html: str) -> List[str]:
"""从HTML中提取文章URL列表"""
soup = BeautifulSoup(html, "lxml")
urls = []
# 新浪娱乐列表页选择器
news_items = soup.select("div.feed_card.ty-feed-card-container div.cardlist-a__list div.ty-card.ty-card-type1")
for item in news_items:
article_link = item.select_one("a")
if article_link:
href = article_link.get('href')
if href:
urls.append(href)
return urls
def _fetch_articles(self, urls: List[str]) -> List[Article]:
"""爬取文章详情"""
articles = []
parser = SinaEntertainmentParser()
for i, url in enumerate(urls[:self.max_articles]):
try:
article = parser.parse(url)
article.category_id = self.category_id
article.source = "新浪"
if not article.author:
article.author = "新浪娱乐"
if article.is_valid():
articles.append(article)
self.logger.info(f"[{i+1}/{len(urls)}] {article.title}")
except Exception as e:
self.logger.error(f"解析文章失败: {url} - {e}")
continue
return articles
2. 创建解析器类
文件:src/parsers/sina_parser.py(在文件末尾添加)
class SinaEntertainmentParser(BaseParser):
"""新浪网娱乐新闻解析器"""
def __init__(self):
self.logger = get_logger(__name__)
self.http_client = HttpClient()
def parse(self, url: str) -> Article:
"""解析新浪网文章详情页"""
html = self.http_client.get(url)
soup = BeautifulSoup(html, "lxml")
# 获取文章标题
article_title_tag = soup.select_one("div.main-content h1.main-title")
article_title = article_title_tag.get_text(strip=True) if article_title_tag else "未知标题"
# 获取文章发布时间
time_tag = soup.select_one("div.main-content div.top-bar-wrap div.date-source span.date")
publish_time = time_tag.get_text(strip=True) if time_tag else "1949-01-01 12:00:00"
# 获取文章作者
author_tag = soup.select_one("div.main-content div.top-bar-wrap div.date-source a")
author = author_tag.get_text(strip=True) if author_tag else "未知"
# 获取文章正文段落
article_div = soup.select_one("div.main-content div.article")
if not article_div:
raise ValueError("无法找到文章内容")
paragraphs = article_div.find_all('p')
content = '\n'.join(p.get_text(strip=True) for p in paragraphs if p.get_text(strip=True))
return Article(
url=url,
title=article_title,
publish_time=publish_time,
author=author,
content=content,
)
3. 更新配置文件
文件:config/config.yaml
sina:
base_url: "https://sina.com.cn"
categories:
auto:
url: "https://auto.sina.com.cn/"
category_id: 6
name: "汽车"
css_selector: "div.feed_card.ty-feed-card-container div.cardlist-a__list div.ty-card.ty-card-type1"
detail_css_selector: "div.main-content"
gov:
url: "https://gov.sina.com.cn/"
category_id: 7
name: "政务"
css_selector: "a[href]"
entertainment: # 新增
url: "https://ent.sina.com.cn/"
category_id: 1
name: "娱乐"
css_selector: "div.feed_card.ty-feed-card-container div.cardlist-a__list"
4. 注册爬虫到 CLI
文件:src/cli/main.py
CRAWLER_CLASSES = {
# ... 其他配置 ...
'sina': {
'auto': ('crawlers.sina.auto', 'SinaAutoCrawler'),
'gov': ('crawlers.sina.gov', 'SinaGovCrawler'),
'entertainment': ('crawlers.sina.entertainment', 'EntertainmentCrawler'), # 新增
},
}
5. 测试运行
# 运行爬虫
python -m src.cli.main sina:entertainment
# 限制数量测试
python -m src.cli.main sina:entertainment --max 3
常见问题
Q1: 如何确定使用 DynamicCrawler 还是 StaticCrawler?
判断方法:
- 使用浏览器查看网页源代码(Ctrl+U)
- 如果源代码中包含完整的文章列表和内容,使用
StaticCrawler - 如果源代码中内容很少,内容通过 JavaScript 动态加载,使用
DynamicCrawler
示例:
- 网易新闻:列表页需要滚动加载 →
DynamicCrawler - 简单的博客网站:内容直接在 HTML 中 →
StaticCrawler
Q2: 如何找到正确的 CSS 选择器?
方法 1: 使用浏览器开发者工具
- 按 F12 打开开发者工具
- 使用元素选择器(Ctrl+Shift+C)点击目标元素
- 在 Elements 面板中,右键点击元素 → Copy → Copy selector
方法 2: 使用 BeautifulSoup 测试
from bs4 import BeautifulSoup
html = """<html>...</html>"""
soup = BeautifulSoup(html, "lxml")
elements = soup.select("div.news-list a")
print(len(elements))
Q3: 爬虫运行失败,如何调试?
步骤:
- 查看日志文件:
logs\crawler.log - 开启调试模式:
python -m src.cli.main example:tech --debug - 手动测试 URL 是否可访问
- 检查 CSS 选择器是否正确
- 检查网站是否有反爬机制(如需要登录、验证码)
常见错误:
未找到新闻列表: CSS 选择器错误解析文章失败: URL 格式错误或网站结构变化HTTP请求失败: 网络问题或被反爬
Q4: 如何处理相对路径的 URL?
href = article_link.get('href')
if href.startswith('/'):
# 相对路径,拼接基础 URL
base_url = "https://www.example.com"
href = base_url + href
elif href.startswith('http'):
# 绝对路径,直接使用
pass
else:
# 其他情况,拼接当前页面的基础路径
href = "https://www.example.com/" + href
Q5: 如何处理时间格式不一致?
import re
from datetime import datetime
def normalize_time(time_str):
"""标准化时间格式"""
# 定义多种时间格式
formats = [
"%Y年%m月%d日 %H:%M",
"%Y-%m-%d %H:%M:%S",
"%Y/%m/%d %H:%M",
"%Y.%m.%d %H:%M",
]
for fmt in formats:
try:
dt = datetime.strptime(time_str, fmt)
return dt.strftime("%Y-%m-%d %H:%M:%S")
except:
continue
# 如果都不匹配,返回默认值
return "1949-01-01 12:00:00"
Q6: 如何提取干净的正文内容?
# 移除不需要的标签
for tag in article_body.select("script, style, iframe, .ad, .comment"):
tag.decompose()
# 提取段落
content_lines = []
for p in article_body.find_all("p"):
text = p.get_text(strip=True)
if text and len(text) > 10: # 过滤太短的段落
content_lines.append(text)
content = '\n'.join(content_lines)
Q7: 如何处理文章重复?
系统自动处理重复:
- 通过 URL 去重
- 通过内容哈希(content_hash)去重
- 使用
INSERT IGNORE语句避免重复插入
查看重复数据:
SELECT url, COUNT(*) as count
FROM news
GROUP BY url
HAVING count > 1;
Q8: 如何批量运行爬虫?
v2.0支持三种运行方式:
# 1. 单个爬虫
python -m src.cli.main tencent:finance --max 5
# 2. 指定多个爬虫(跨数据源)
python -m src.cli.main tencent:finance tencent:tech netease:tech --max 3
# 3. 所有爬虫
python -m src.cli.main --all --max 5
统计功能自动启用:
- 单个爬虫:显示简明统计
- 多个爬虫:显示按数据源分组的汇总统计
Q9: 如何修改最大爬取数量?
方法 1: 命令行参数
python -m src.cli.main example:tech --max 20
方法 2: 配置文件
编辑 config/config.yaml:
crawlers:
max_articles: 20 # 修改全局默认值
Q10: 爬虫运行很慢,如何优化?
优化策略:
- 减少
max_articles数量 - 调整
selenium.scroll_pause_time(滚动暂停时间) - 减少
selenium.max_scroll_times(最大滚动次数) - 使用
StaticCrawler代替DynamicCrawler(如果可能)
配置示例:
selenium:
scroll_pause_time: 0.5 # 减少暂停时间
max_scroll_times: 3 # 减少滚动次数
附录
A. 数据库表结构
CREATE TABLE `news` (
`id` int NOT NULL AUTO_INCREMENT,
`url` varchar(500) NOT NULL COMMENT '文章URL',
`title` varchar(500) NOT NULL COMMENT '文章标题',
`content` text COMMENT '文章内容',
`category_id` int NOT NULL COMMENT '分类ID',
`publish_time` varchar(50) DEFAULT NULL COMMENT '发布时间',
`author` varchar(100) DEFAULT NULL COMMENT '作者',
`source` varchar(50) DEFAULT NULL COMMENT '新闻源',
`content_hash` varchar(64) DEFAULT NULL COMMENT '内容哈希',
`created_at` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`),
UNIQUE KEY `url` (`url`),
KEY `content_hash` (`content_hash`),
KEY `category_id` (`category_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='新闻表';
B. 分类表结构
CREATE TABLE `news_category` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL COMMENT '分类名称',
`code` varchar(50) NOT NULL COMMENT '分类代码',
`description` varchar(200) DEFAULT NULL COMMENT '描述',
`sort_order` int DEFAULT 0 COMMENT '排序',
PRIMARY KEY (`id`),
UNIQUE KEY `code` (`code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='新闻分类表';
C. 常用 CSS 选择器示例
# 通过 ID 选择
soup.select_one("#article-title")
# 通过 class 选择
soup.select_one(".article-title")
soup.select_one("div.article-title")
# 通过属性选择
soup.select_one("a[href^='/article/']")
# 组合选择
soup.select_one("div.news-list div.news-item a.title")
# 多层级选择
soup.select_one("div.main-content > div.article > p")
# 伪类选择
soup.select_one("ul.news-list li:first-child a")
D. BeautifulSoup 常用方法
# 获取文本
element.get_text(strip=True)
# 获取属性
element.get('href')
element.get('class')
# 查找单个元素
soup.select_one("div.title")
soup.find("div", class_="title")
# 查找多个元素
soup.select("div.news-item")
soup.find_all("div", class_="news-item")
# 父元素和子元素
parent = element.parent
children = element.children
E. 项目依赖
查看 requirements.txt:
requests>=2.31.0
beautifulsoup4>=4.12.0
lxml>=4.9.0
selenium>=4.15.0
PyYAML>=6.0
总结
添加新爬虫的核心步骤:
- ✅ 分析目标网站结构(确定使用API/静态/动态方式)
- ✅ 创建爬虫类(继承
DynamicCrawler或StaticCrawler) - ✅ 创建解析器类(继承
BaseParser) - ✅ 更新配置文件(
config.yaml) - ✅ 注册爬虫到 CLI(
src/cli/main.py) - ✅ 测试运行(单个/多个/全部)
遵循本指南,您可以为新闻爬虫系统添加任意数量的新网站和分类爬虫。
文档版本: 2.0 最后更新: 2026-01-17 维护者: 新闻爬虫项目组
版本更新说明:
- v2.0: 新增API爬虫类型、多爬虫支持、统计展示功能
- v1.0: 初始版本