Skip to content

Latest commit

 

History

9 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

English

FGDigitalFetcher — 农产品价格爬虫

背景:FGDigitalFetcher 是我实习期间开始做的一款农产品价格爬虫。当时我主要负责开发 service 层和 crawler 层;此后我独立补全了剩余的应用部分——自建 HTTP API、常驻调度器、SQLite 持久化、数据看板、SPA 抓取(BrowserFetcher)等,完成了完整的软件架构。

FGDigitalFetcher 是一个用 Go 编写的农产品价格抓取服务。它从惠农网、肉交所等站点抓取当日行情,支持按省份遍历全国、按中文品种名或关键词检索,内置 Nuxt2 解包器与反爬限速,数据经清洗校验后持久化到 SQLite。

功能特性

  • 多站点支持:已接入惠农网(农产品行情)、肉交所(肉类挂牌价),新增站点只需一个 Extractor 文件
  • 全国省份遍历:每个站点维护独立的「省份 → 站点 ID」映射(两站编号体系不同),省份简称/官方全称统一归一,一键遍历全国各省
  • 品种中文搜索:惠农网(63 个顶级品类 + 辣椒 29 个子品种)与肉交所(8 大肉类 + 约 100 个子分类)都支持中文名定位;肉交所还支持任意中文关键词搜索
  • 语义字段提取:调用方只传字段名(如 price_list、avg_price),字段提取规则由站点 Extractor 内部维护,页面改版只改 Extractor 一处
  • Nuxt2 IIFE 解包器:惠农网行情数据内嵌在压缩 JS 中,内置纯 Go 解包器还原为 JSON,无需浏览器或 Node
  • 反爬防御:请求间隔带随机抖动([-21%, +100%)),时序接近真人,降低被封概率
  • 数据清洗管线:去空白 → 内容校验 → 字段校验 → 类型转换 → 归一化,可插拔组合
  • SQLite 持久化:WAL 模式、软删除、URL 去重(TTL),零外部依赖
  • 主动拉取模式:自建 HTTP API + Scheduler 常驻调度,任务入队 → 定时拉取 → 抓取 → 入库全自动

技术栈

组件 选型 说明
语言 Go 1.26 标准库 + goquery + modernc SQLite
抓取 net/http HTTP 直取服务端渲染页面
HTML 解析 goquery jQuery 风格选择器,用于肉交所等 HTML 站点
数据提取 encoding/json 惠农网 __NUXT__ 数据、Extractor 输出
存储 modernc.org/sqlite 纯 Go 驱动,无 CGO,Windows/Linux 直接编译

快速开始

# 构建
go build ./...

# 运行全部测试
go test ./...

# 静态检查
go vet ./...

运行 CLI

# 抓惠农网辣椒(广西)
go run ./cmd/fetcher --site cnhnb --province 广西

# 抓惠农网辣椒(全国各省)
go run ./cmd/fetcher --site cnhnb --all-provinces

# 抓全国各省的朝天椒
go run ./cmd/fetcher --site cnhnb --category 朝天椒 --all-provinces

# 中文模糊搜索品种
go run ./cmd/fetcher --search 椒

# 直接抓指定 URL 并写入 SQLite
go run ./cmd/fetcher --url https://www.cnhnb.com/hangqing/lajiao/ --save

本地数据看板

常驻服务内置数据看板,启动后浏览器打开 http://localhost:8080/ 即可用:

go run ./cmd/fetcherd            # 或 go run ./cmd/fetcherd --config config.yaml

惠农网行情

惠农网行情模块(元/斤)

肉交所行情

肉交所挂牌模块(元/吨)

看板支持:按站点(惠农网 / 肉交所)分模块汇总结果、品种搜索选择(惠农网 63 品类 + 肉交所 8 大类)、省份筛选、一键触发抓取(完成后自动刷新);不常驻轮询,数据用手动「刷新」按钮拉取。

架构概览

flowchart TB
    API["HTTP API(自建)"]
    SVC["Service 层(Scheduler 常驻调度)"]
    ORCH["Orchestrator 编排中心<br/>URL 校验 → 路由 → 限流(抖动)<br/>→ 抓取 → 提取 → Pipeline 清洗"]
    CRL["Crawler 层:fetcher → extractor → pipeline<br/>Store 层:SQLite 持久化 + 去重"]

    API <-->|"定时拉取任务 / 回传结果"| SVC
    SVC --> ORCH
    ORCH --> CRL
Loading

核心数据流

flowchart TB
    subgraph s1["请求接入与校验"]
        direction LR
        A["API 传 {URL, 字段列表}"] --> B["validateURL 校验"] --> C["Match 路由到站点 Extractor"] --> D["RateLimit 等待(含抖动)"]
    end
    subgraph s2["抓取处理与入库"]
        direction RL
        E["Fetcher 抓取页面"] --> F["Extractor 按字段名提取"] --> G["Pipeline 清洗校验"] --> H["SQLite 入库 + 返回 ScrapeResult"]
    end
    D --> E
Loading

目录结构

fetcher/
├── cmd/
│   ├── fetcher/
│   │   └── main.go               # 一次性 CLI 入口(--site / --province / --category / --search 等)
│   └── fetcherd/
│       └── main.go               # 常驻服务入口(HTTP API + Scheduler)
├── internal/
│   ├── crawler/
│   │   ├── fetcher/              # 抓取层:HTTPFetcher、BrowserFetcher(chromedp,Edge/Chrome)
│   │   ├── extractor/            # 抽取层
│   │   │   ├── interface.go      # Extractor 接口 + ProvinceURLBuilder / CategoryURLBuilder
│   │   │   ├── registry.go       # 注册表:Register / Match / All
│   │   │   ├── base.go           # 公共工具方法
│   │   │   └── sites/            # ★ 站点实现(每站一个子包,自注册)
│   │   │       ├── cnhnb/              # 惠农网:农产品行情(元/斤)
│   │   │       │   ├── hangqing.go     # Nuxt2 __NUXT__ 解包 + 行情提取
│   │   │       │   ├── provinces.go    # 省份→地区ID 映射 + ProvinceURL
│   │   │       │   ├── categories.go   # 品种中文名→品类ID 映射
│   │   │       │   └── testdata/       # 真实页面 fixture
│   │   │       └── roujiaosuo/         # 肉交所:肉类挂牌价(元/吨)
│   │   │           ├── mall.go         # goquery 解析商品表格
│   │   │           ├── provinces.go    # 省份→仓库ID 映射 + ProvinceURL
│   │   │           ├── categories.go   # 分类→catid 映射 + kw 关键词搜索
│   │   │           └── testdata/       # 真实页面 fixture
│   │   ├── orchestrator/         # 编排中心:完整流水线 + 限流抖动
│   │   ├── pipeline/             # 清洗管线:5 个可插拔 Step
│   │   └── middleware/           # 站点级限流器(跨并发任务错峰)
│   ├── geo/                      # 省份别名归一化(简称/官方全称 → 短名)
│   ├── model/                    # 数据模型:Page、ScrapeResult、Task、FieldError 等
│   ├── store/                    # 持久化层:SQLite 实现 + 接口抽象(结果 + 任务)
│   ├── task/                     # 语义请求 → URL 列表展开(CLI 与 API 共用)
│   └── service/                  # Service 层:Scheduler + HTTP API + 数据看板(dashboard.html)
├── config/
│   └── config.go                 # 配置结构 + Default + Load(YAML)
├── config.yaml                   # 配置文件示例
├── docs/
│   ├── architecture.md           # 架构设计文档
│   └── dev-plan.md               # 开发计划
└── LICENSE                       # Apache-2.0

已接入站点

站点 文件 数据 价格单位 状态
惠农网 cnhnb.com sites/cnhnb/ 各产地当日行情 元/斤 ✅ 可用
肉交所 roujiaosuo.com sites/roujiaosuo/ 肉类商品挂牌价 元/吨 ✅ 可用

站点筛选说明

两站的省份 ID 体系互不相同,因此每个站点维护独立的「省→ID」映射(各站子包内的 provinces.go),省份输入经 internal/geo 归一化(简称/官方全称均可,如「广西」「广西壮族自治区」都归一到「广西」):

站点 省份维度 映射文件 备注
惠农网 URL 第 3 段「省份 ID」 cnhnb/provinces.go 编号有空档(缺 1/2/9/22),直辖市在 45081~45084,广西=20
肉交所 cangku 参数 roujiaosuo/provinces.go 0~34 连续(0=全部,广西=21,天津=3)

品种维度:惠农网支持中文品种名(cnhnb/categories.go,63 个顶级品类 + 辣椒 29 个子品种;全国搜索走 slug 路由,按省筛选需 cateId、部分品类暂缺);肉交所支持中文分类名(roujiaosuo/categories.go,8 大肉类 + 约 100 个子分类),且未命中分类表的输入会自动降级为 kw 关键词搜索,因此肉交所也支持「猪肉」「五花肉」等任意中文词。

扩展一个新站点

  1. 在 internal/crawler/extractor/sites/ 下新建一个站点子包(如 sites/your_site/)
  2. 实现 Extractor 接口(Match / FetcherType / Extract / RateLimit / AuthProvider)
  3. init() 中调用 extractor.Register(&YourExtractor{}) 自注册
  4. 在 cmd/fetcher 的 import 里加一行空导入,触发自注册
  5. 在子包 testdata/ 放真实页面 fixture 写测试

详见 docs/architecture.md 第 7 节。

当前进度

阶段 状态
Fetcher 抓取层 ✅ 完成
Extractor 抽取层(惠农网 + 肉交所) ✅ 完成
Orchestrator 编排中心 ✅ 完成(含集成测试)
Pipeline 管线层 ✅ 完成
Store 持久化层(SQLite) ✅ 完成
全国省份遍历 + 品种中文搜索 ✅ 完成
Service 调度层 + HTTP API ✅ 完成
BrowserFetcher(SPA 页面,Edge/Chrome) ✅ 完成

合规说明

抓取遵循以下原则:

  • 遵守目标站点的 robots.txt 和服务条款
  • 限速抓取(RateLimit + 随机抖动),不对抗式绕过反爬
  • 仅采集公开数据,不采集个人信息

License

Apache-2.0

About

This project began as an internship assignment where I built the service and crawler layers for an agricultural price scraper. I have now expanded it into a fully functional application, independently implementing the complete backend logic and architecture.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages