本项目是一个基于 Scrapy 框架下的爬虫脚本,旨在抓取校园网各页面,并索引进数据库。
爬虫范围:
- 公开页面
该项目作为 上应小风筝 的一部分发布。 爬虫将缓存站点网页内容和一些小一点的附件,以供后续为用户提供校园网内的文件、网页检索服务。
项目爬虫部分的三名作者曾经有过一些 Python 语言的经验,但是均为第一次接触 Scrapy 框架。 而公开页面爬虫与网上流行的针对特定条目、字段进行爬取的示例不同,本项目作为一个通用的爬虫脚本, 因而作者在探索优雅地基于 Scrapy 框架编写整站爬虫过程中,踩过不少坑。 限于精力,当前版本的项目,存在一定的效率问题,(可能)将在今后不断地改善。
该项目为 上海应用技术大学-网站 设计,不保证对其他站点有效。
主要开发和生产环境软件(及语言库)版本如下:
- Windows 10
- Ubuntu 20.04 LTS
- FreeBSD 12.2
- Python 3.7, 3.9
- Scrapy 2.4.1
- PyCharm 2020.3
- PostgreSQL 13.2
- pg_jieba
请先修改 kite/settings.py 的配置选项:
# 数据库配置选项
PG_DATABASE = 'db'
PG_USERNAME = 'user'
PG_PASSWORD = 'password'
PG_PORT = 5432
PG_HOST = 'ip'
# 附件存储路径
FILES_STORE = 'download'项目支持自动加载 .env(通过 python-dotenv),你也可以把这些变量写到仓库根目录 .env:
PG_DATABASE=db
PG_USERNAME=user
PG_PASSWORD=password
PG_HOST=127.0.0.1
PG_PORT=5432
FILES_STORE=download如果你希望将附件和图片直接存入 S3,请使用环境变量覆盖:
export FILES_STORE='s3://your-bucket/kite'
export FILES_STORE_S3_ACL='private'
export AWS_ACCESS_KEY_ID='xxx'
export AWS_SECRET_ACCESS_KEY='xxx'
# 可选:自建对象存储(如 MinIO/Ceph)
export AWS_ENDPOINT_URL='https://s3.example.com'
export AWS_REGION_NAME='ap-east-1'说明:
- 页面正文元信息写入
pages表。 - 附件和图片的元信息统一写入
attachments表。 - 附件元数据分词写入
attachments.search_vector,覆盖title、文件名、ext、referer。 - 附件全文索引写入
attachment_content表,并维护attachment_content.search_vector。 - 文件实体存储在
FILES_STORE指向的位置(本地目录或 S3)。 - 全文检索使用
pg_jieba的jiebaqry配置,并维护pages.search_vector列(title权重 A,content权重 B)。
如有需要,可以在 kite/spiders/__init__.py 中修改已知的扩展名列表:
page_postfix_set = {
'asp', 'aspx', 'jsp', 'do', 'htm', 'html', 'php', 'cgi', '/', 'portal', 'action'
}
attachment_postfix_set = {
# '7z', 'zip', 'rar',
'xls', 'xlsx', 'doc', 'docx', 'ppt', 'pptx', 'pdf'
}根据经验,抓取上海应用技术大学网站时,请为下载目录 FILES_STORE 预留至少 4GB 空间,并确保数据库路径存有 500MB 空间。
如果您运行 public 爬虫,大部分页面是可以在公网访问到的。抓取速度受网络和校方服务器限制,调高 settings.py 中并发所带来的收益并不大。
在并发请求数设置为 128 的情况下,作者最近一次运行 public 爬虫抓取用时约 4 小时,实际并发连接数保持在 100 左右。
截至 README.md 文档修改时,公开网页约 12 万篇(过滤后超 5 万篇),附件近 1 万个。
执行
git clone https://github.com/sunnysab/kite-string
cd kite-string
# 安装 uv(如未安装)
# https://docs.astral.sh/uv/getting-started/installation/
# 安装项目依赖
uv sync
# 运行静态检查
uv run --group dev ruff check .
# 如果需要附件正文索引,请额外安装 tools 依赖
uv sync --extra tools
# 可选:老式 .doc 建议安装 antiword 与 catdoc(任一可用即可,两个都装更稳)
# 例如 Arch Linux:
# sudo pacman -S --needed antiword catdoc
# 可选:.doc / .ppt 也可通过 LibreOffice 转 PDF 后再提取
# 例如 Ubuntu / Debian:
# sudo apt-get install -y libreoffice
# ...此外还需要配置数据库
# 执行公开网页爬虫
uv run scrapy crawl public如果上次中断后重跑发现“没有新页面进入队列”,可以先确认你是否需要重新发现入口/索引页。
当前默认去重器只会加载数据库中满足以下条件的页面 URL:
path以/info开头content非空(去掉空白后长度大于 0)
其他页面(例如大量索引页)默认不会在启动时从数据库预加载去重。
可先执行一次“重新发现页面”的模式:
uv run scrapy crawl public -s KITE_DUPEFILTER_LOAD_EXISTING_URLS=0说明:
KITE_DUPEFILTER_LOAD_EXISTING_URLS=0:本次运行不加载数据库历史 URL,只保留本轮内去重。- 页面/附件入库仍走
ON CONFLICT ... DO UPDATE,不会因为重复 URL 直接插入脏数据。
如果你希望中断后继续上一次任务(而不是从入口页重新发现),建议固定 JOBDIR:
uv run scrapy crawl public -s JOBDIR=.scrapy/jobs/public之后即使手动停止,也可以用同一命令继续。
假设:
- 你已经执行过
uv run --extra tools python tools/migrate_attachment_search.py,或手动应用过kite.sql中新增的附件检索相关语句。 - PostgreSQL 已安装
pg_jieba。 - 若
FILES_STORE使用本地目录,脚本会直接读取本地文件。 - 若
FILES_STORE使用s3://bucket/prefix,脚本会按当前AWS_*配置读取对象存储中的附件。
当前行为:
- 元数据检索:
attachments.search_vector - 全文检索:
attachment_content.search_vector - 已支持:
pdf、doc、docx、ppt、pptx、xls、xlsx - 直接解析:
pdf、docx、pptx、xls、xlsx - 兜底解析:
doc优先走antiword,失败时再尝试catdoc,最后再尝试libreoffice --headless;ppt走libreoffice --headless - 增量策略:新附件或
checksum变化时,自动写入/更新attachment_content.status = 'pending'
执行:
# 先应用附件检索迁移
uv run --extra tools python tools/migrate_attachment_search.py
# 增量索引最近未处理、失败或 checksum 变化的附件
uv run --extra tools python tools/index_attachment_content.py
# 只重建 PDF 附件
uv run --extra tools python tools/index_attachment_content.py --ext pdf --force
# 只处理指定附件
uv run --extra tools python tools/index_attachment_content.py --attachment-id 123
# 如果你补装了 antiword / catdoc / libreoffice,需要重试历史失败项
uv run --extra tools python tools/index_attachment_content.py --retry-failed验证:
SELECT id, title, ext
FROM attachments
WHERE search_vector @@ to_tsquery('jiebaqry', '通知 & pdf');
SELECT a.id, a.title, ac.status
FROM attachment_content AS ac
JOIN attachments AS a ON a.id = ac.attachment_id
WHERE ac.search_vector @@ to_tsquery('jiebaqry', '奖学金');
SELECT *
FROM public.search_attachment('奖学金', NULL, NULL, 10, 0, true);本地查询:
# 搜附件元数据 + 正文
uv run --extra tools python tools/search_attachment.py 奖学金
# 只搜 PDF
uv run --extra tools python tools/search_attachment.py 通知 --ext pdf
# 只搜元数据
uv run --extra tools python tools/search_attachment.py 招生 --metadata-only
# 过滤指定站点
uv run --extra tools python tools/search_attachment.py 转专业 --host jwc.sit.edu.cn
# 页面 + 附件一起搜
uv run --extra tools python tools/search_site_content.py 奖学金
# 只搜页面
uv run --extra tools python tools/search_site_content.py 奖学金 --pages-only
# 只搜附件,并限制附件扩展名
uv run --extra tools python tools/search_site_content.py 通知 --attachments-only --attachment-ext pdf仓库内已提供用户级 systemd 定时任务配置:
tools/auto_sync.sh:执行脚本、检查仓库状态、自动commit + pushdeploy/systemd/user/kite-string-auto-sync.servicedeploy/systemd/user/kite-string-auto-sync.timer
默认执行命令是:
uv run scrapy crawl public定时规则是每天本地时间 08:00 和 16:00 各执行一次;若机器在该时间点关机,开机后会补跑一次。
mkdir -p ~/.config/systemd/user
ln -sf /home/sab/kite-string/deploy/systemd/user/kite-string-auto-sync.service ~/.config/systemd/user/kite-string-auto-sync.service
ln -sf /home/sab/kite-string/deploy/systemd/user/kite-string-auto-sync.timer ~/.config/systemd/user/kite-string-auto-sync.timer
systemctl --user daemon-reload
systemctl --user enable --now kite-string-auto-sync.timersystemctl --user status kite-string-auto-sync.timer
systemctl --user list-timers kite-string-auto-sync.timer --all
journalctl --user -u kite-string-auto-sync.service -n 50 --no-pager如果你想覆盖默认行为,可以在启动 service 前注入这些环境变量:
AUTO_RUN_COMMAND:实际执行的命令AUTO_COMMIT_MESSAGE_PREFIX:提交标题,默认是chore: scheduled updateGIT_REMOTE:默认是originGIT_BRANCH:默认取当前分支RUN_TIMEOUT:默认是3h
脚本还会做这些保护:
- 若运行前仓库已有未提交改动,会直接退出,避免误提交你的手工修改
- 若已有同一任务在运行,会直接跳过,避免并发重复跑
- 若本次执行后没有文件变化,不会创建空提交
如果你想针对其他网站抓取,你可能要修改 public.py - PublicPageSpider 中的 starts_urls,以及其 parse 函数中这样一段代码:
if host != 'sit.edu.cn' and not host.endswith('.sit.edu.cn'):
continue祝你好运!
- Windows 下无法使用
pip直接安装Twisted库怎么办?
可以去 Pypi 网站上 下载 一个编译好的包安装。
-
lxml库安装报错Please make sure the libxml2 and libxslt development packages are installed.可以使用
pkg安装这两个包,再pip install lxml即可。 -
psycopg安装报错:pg_config executable not found.使用
pkg安装postgresql13-client。项目使用psycopg3.x,建议在虚拟环境中安装最新稳定版。sudo pkg install postgresql13-client pip install psycopg
(小技巧:使用
pkg search可以搜索相关的包。如,等到 FreeBSD 普遍使用 python38, python39 时,这些包名也会相应改变。) -
cryptography安装报错:can't find Rust compiler。这是因为一个 OpenSSL 相关的库依赖了
cryptography库。 这里尝试使用pkg安装:sudo pkg install py37-cryptography (划掉
但是会带来问题:全局环境中安装的包,和虚拟环境中安装的,并不互通。也就是说,使用
pkg安装后,虚拟环境中不存在对应包。 这时候,要么放弃虚拟环境,要么安装 Rust 编译环境。其实 Rust 编译环境也很好装:sudo pkg install rust
pkg 源中的 rust 版本总体还是蛮新的,可能会晚一个版本。相比 linux 下 apt 源更新的速度好多了。 现在再用
pip安装即可。 -
运行时报错
2021-04-06 16:23:33 [twisted] CRITICAL: Traceback (most recent call last): File "/home/sunnysab/kite-string/venv/lib/python3.7/site-packages/twisted/internet/defer.py", line 1445, in _inlineCallbacks result = current_context.run(g.send, result) File "/home/sunnysab/kite-string/venv/lib/python3.7/site-packages/scrapy/crawler.py", line 87, in crawl self.engine = self._create_engine() File "/home/sunnysab/kite-string/venv/lib/python3.7/site-packages/scrapy/crawler.py", line 101, in _create_engine return ExecutionEngine(self, lambda _: self.stop()) File "/home/sunnysab/kite-string/venv/lib/python3.7/site-packages/scrapy/core/engine.py", line 67, in __init__ self.scheduler_cls = load_object(self.settings['SCHEDULER']) File "/home/sunnysab/kite-string/venv/lib/python3.7/site-packages/scrapy/utils/misc.py", line 62, in load_object mod = import_module(module) File "/usr/local/lib/python3.7/importlib/__init__.py", line 127, in import_module return _bootstrap._gcd_import(name[level:], package, level) File "<frozen importlib._bootstrap>", line 1006, in _gcd_import File "<frozen importlib._bootstrap>", line 983, in _find_and_load File "<frozen importlib._bootstrap>", line 967, in _find_and_load_unlocked File "<frozen importlib._bootstrap>", line 677, in _load_unlocked File "<frozen importlib._bootstrap_external>", line 728, in exec_module File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed File "/home/sunnysab/kite-string/venv/lib/python3.7/site-packages/scrapy/core/scheduler.py", line 7, in <module> from queuelib import PriorityQueue File "/home/sunnysab/kite-string/venv/lib/python3.7/site-packages/queuelib/__init__.py", line 1, in <module> from queuelib.queue import FifoDiskQueue, LifoDiskQueue File "/home/sunnysab/kite-string/venv/lib/python3.7/site-packages/queuelib/queue.py", line 5, in <module> import sqlite3 File "/usr/local/lib/python3.7/sqlite3/__init__.py", line 23, in <module> from sqlite3.dbapi2 import * File "/usr/local/lib/python3.7/sqlite3/dbapi2.py", line 27, in <module> from _sqlite3 import * ModuleNotFoundError: No module named '_sqlite3'
观察到出错的 sqlite3 位于 /usr/local/lib/python3.7/sqlite3 下,可以使用 pkg 重装 sqlite3
模块。执行 sudo pkg install py37-sqlite3-3.7.10_7 (视实际情况定)即可解决。
- 19级 化工 @OneofFive-ops
项目各模块的奠基人 :D - 18级 计算机 @sunnysab
项目重构、维护,数据库和分词的对接 - 18级 计算机 @Wanfengcxz
PDF 文件解析
项目代码基于 GPL v3 协议授权,协议全文