Documentation
¶
Overview ¶
Package feedexport 实现了 scrapy-go 框架的数据导出(Feed Export)系统。
概述 ¶
feedexport 包将爬取的 Item 数据导出为多种格式(JSON、JSON Lines、CSV、XML), 并支持多种存储后端(本地文件、标准输出)。 对应 Scrapy Python 版本中 scrapy.extensions.feedexport 和 scrapy.exporters 模块的功能。
核心类型 ¶
本包提供以下核心类型:
- ItemExporter:序列化器接口,将 Item 编码为指定格式的字节流
- FeedStorage:存储后端接口,管理导出文件的打开、写入和持久化
- FeedSlot:组合 Exporter 与 Storage,代表一个正在进行中的导出任务
- FeedConfig:Feed 导出配置(格式、路径、字段白名单等)
- ExporterOptions:Exporter 的通用配置
- ItemFilterFunc:Item 过滤函数类型
内置 Exporter ¶
本包提供以下内置序列化器:
┌────────────────────────────────────────────────────────┐ │ 格式 │ 类型 │ 说明 │ ├────────────────────────────────────────────────────────┤ │ JSON │ JSONExporter │ JSON 数组格式 │ │ JSON Lines │ JSONLinesExporter │ 每行一个 JSON │ │ CSV │ CSVExporter │ 逗号分隔值 │ │ XML │ XMLExporter │ XML 文档格式 │ └────────────────────────────────────────────────────────┘
内置 Storage ¶
- FileStorage:本地文件存储(支持路径模板变量)
- StdoutStorage:标准输出存储(用于调试和管道输出)
使用方式 ¶
通过 Crawler 注册 Feed 导出:
c := crawler.New()
c.AddFeed(feedexport.FeedConfig{
URI: "output/items.json",
Format: feedexport.FormatJSON,
Fields: []string{"title", "price", "url"},
})
多格式同时导出:
c.AddFeed(feedexport.FeedConfig{URI: "items.jsonl", Format: feedexport.FormatJSONLines})
c.AddFeed(feedexport.FeedConfig{URI: "items.csv", Format: feedexport.FormatCSV})
使用标准输出(调试):
c.AddFeed(feedexport.FeedConfig{URI: "stdout:", Format: feedexport.FormatJSON})
Exporter 生命周期 ¶
ItemExporter 的调用顺序:
- StartExporting — 开始导出(写入格式前缀,如 JSON 的 "[")
- ExportItem — 反复调用,每次序列化一个 Item
- FinishExporting — 结束导出(写入格式后缀,如 JSON 的 "]")
FeedSlot 工作流 ¶
FeedSlot 组合 Exporter 和 Storage 的完整工作流:
- Open:通过 Storage.Open 获取 io.WriteCloser
- 创建 Exporter 并调用 StartExporting
- 每个 Item 通过 ItemFilterFunc 过滤后调用 ExportItem
- Close:调用 FinishExporting,然后通过 Storage.Store 持久化
Item 序列化 ¶
所有 Exporter 内部使用 item.Adapt 将 Item 转为统一的字段访问接口, 然后根据 ExporterOptions.FieldsToExport 决定导出哪些字段。 这使得 Exporter 可以无差别地处理 map 和 struct 类型的 Item。
与 Scrapy 的差异 ¶
- 舍弃 S3/GCS/FTP 等远程存储后端,仅保留本地文件和标准输出
- 舍弃 PostProcessingManager(gzip/zstd 压缩),可通过 io.Writer 包装实现
- 舍弃 ItemFilter 的动态加载机制,改为 ItemFilterFunc 函数类型
- 基于 io.Writer 而非 Python 的文件句柄抽象
- 使用 item.Adapt 统一 Item 访问,替代 Python 的 ItemAdapter
- Format 使用字符串常量而非类引用
- NormalizeFormat 兼容常见别名("jl"/"jsonl"/"jsonlines")
Package feedexport 的字段序列化器注册表。
对齐 Scrapy 的 BaseItemExporter.serialize_field 机制: Exporter 在写入每个字段前,根据 FieldMeta 中的 "serializer" 键查表调用 已注册的序列化函数,将原始值转换为导出值。
典型用法:
type Product struct {
Price float64 `item:"price,serializer=to_int"`
}
func init() {
feedexport.RegisterSerializer("to_int", func(v any) any {
if f, ok := v.(float64); ok {
return int(f)
}
return v
})
}
Index ¶
- func AcceptAll(_ any) bool
- func ClearSerializers()
- func RegisterExporter(format Format, factory ExporterFactory)
- func RegisterSerializer(name string, fn SerializeFunc)
- func SerializeField(meta item.FieldMeta, name string, value any) any
- type CSVExporter
- type ExporterFactory
- type ExporterOptions
- type FeedConfig
- type FeedSlot
- type FeedStorage
- type FileStorage
- type Format
- type ItemExporter
- type ItemFilterFunc
- type JSONExporter
- type JSONLinesExporter
- type SerializeFunc
- type StdoutStorage
- type URIParams
- type XMLExporter
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func AcceptAll ¶
AcceptAll 是默认过滤器,接受所有 Item。
Example ¶
ExampleAcceptAll 演示默认的 Item 过滤器。
package main
import (
"fmt"
"github.com/dplcz/scrapy-go/pkg/feedexport"
)
func main() {
// AcceptAll 接受所有 Item
fmt.Println(feedexport.AcceptAll(map[string]any{"title": "test"}))
fmt.Println(feedexport.AcceptAll(nil))
fmt.Println(feedexport.AcceptAll("anything"))
}
Output: true true true
func RegisterExporter ¶
func RegisterExporter(format Format, factory ExporterFactory)
RegisterExporter 注册一个自定义 Exporter 工厂函数。 若 format 已存在,会被覆盖。线程安全。
func RegisterSerializer ¶
func RegisterSerializer(name string, fn SerializeFunc)
RegisterSerializer 注册一个命名的字段序列化函数。 若 name 已存在,会被覆盖。线程安全。
对齐 Scrapy 的 Field(serializer=func) 机制,但采用显式注册表模式 替代 Python 的动态函数引用。
用法:
feedexport.RegisterSerializer("to_int", func(v any) any {
if f, ok := v.(float64); ok {
return int(f)
}
return v
})
func SerializeField ¶
SerializeField 对齐 Scrapy 的 BaseItemExporter.serialize_field 方法。
逻辑:
- 从 FieldMeta 中读取 "serializer" 键
- 若命中已注册的 SerializeFunc,调用并返回转换后的值
- 未命中则回退返回原始值(identity)
参数:
- meta: 字段元数据(可为 nil)
- name: 字段名(用于日志/调试,当前未使用)
- value: 原始字段值
Types ¶
type CSVExporter ¶
type CSVExporter struct {
// contains filtered or unexported fields
}
CSVExporter 将 Item 序列化为 CSV 格式。 对应 Scrapy 的 CsvItemExporter。
特性:
- 第一行可选为字段名标题行(通过 IncludeHeadersLine 控制)
- 切片字段自动用 JoinMultivalued 连接(默认 ",")
- 字段顺序来自首个 Item,除非 FieldsToExport 显式指定
- 复杂类型(map、嵌套 struct)通过 fmt.Sprintf("%v") 格式化
注意:CSV 格式的字段集合必须在所有 Item 间保持一致——首个 Item 的字段集合 将作为表头,后续 Item 中不存在的字段输出空字符串。
func NewCSVExporter ¶
func NewCSVExporter(w io.Writer, opts ExporterOptions) *CSVExporter
NewCSVExporter 创建一个 CSV 格式的 Exporter。
func (*CSVExporter) ExportItem ¶
func (e *CSVExporter) ExportItem(item any) error
ExportItem 输出一行 CSV 记录。
func (*CSVExporter) FinishExporting ¶
func (e *CSVExporter) FinishExporting() error
FinishExporting 刷新缓冲并标记导出结束。
func (*CSVExporter) StartExporting ¶
func (e *CSVExporter) StartExporting() error
StartExporting 标记开始导出。 若 FieldsToExport 已指定,会立即写入表头;否则延迟到首个 Item 写入时确定。
type ExporterFactory ¶
type ExporterFactory func(w io.Writer, opts ExporterOptions) ItemExporter
ExporterFactory 根据指定的 Writer 和 Options 构造一个 ItemExporter。 框架内置了所有核心格式的工厂函数,用户也可以通过 RegisterExporter 注册自定义格式。
func LookupExporter ¶
func LookupExporter(format Format) (ExporterFactory, bool)
LookupExporter 按格式查找 Exporter 工厂。 返回 nil 和 false 表示未注册。
type ExporterOptions ¶
type ExporterOptions struct {
// Encoding 指定文本编码(如 "utf-8")。
// 空字符串表示使用默认编码(通常为 utf-8)。
Encoding string
// Indent 指定缩进空格数。
// - 0 或负值:紧凑输出(不缩进)
// - > 0 :按此空格数进行缩进
// 仅 JSON 与 XML 使用;CSV / JSON Lines 忽略此字段。
Indent int
// FieldsToExport 指定要导出的字段白名单。
// 为空时:导出 Item 的全部字段。
// 导出顺序与 FieldsToExport 保持一致。
FieldsToExport []string
// IncludeHeadersLine 仅对 CSV 生效,是否在第一行输出字段名。
// 默认 true。
IncludeHeadersLine bool
// JoinMultivalued 仅对 CSV 生效,用于将切片字段连接为单个字符串的分隔符。
// 默认 ","。
JoinMultivalued string
// ItemElement 仅对 XML 生效,每个 Item 对应的元素名。默认 "item"。
ItemElement string
// RootElement 仅对 XML 生效,XML 根元素名。默认 "items"。
RootElement string
}
ExporterOptions 是 Exporter 的通用配置。 不同 Exporter 可能忽略部分字段。
func DefaultExporterOptions ¶
func DefaultExporterOptions() ExporterOptions
DefaultExporterOptions 返回默认配置。
Example ¶
ExampleDefaultExporterOptions 演示默认导出配置。
package main
import (
"fmt"
"github.com/dplcz/scrapy-go/pkg/feedexport"
)
func main() {
opts := feedexport.DefaultExporterOptions()
fmt.Println("Encoding:", opts.Encoding)
fmt.Println("Indent:", opts.Indent)
fmt.Println("IncludeHeaders:", opts.IncludeHeadersLine)
fmt.Println("JoinMultivalued:", opts.JoinMultivalued)
fmt.Println("ItemElement:", opts.ItemElement)
fmt.Println("RootElement:", opts.RootElement)
}
Output: Encoding: utf-8 Indent: 0 IncludeHeaders: true JoinMultivalued: , ItemElement: item RootElement: items
type FeedConfig ¶
type FeedConfig struct {
// URI 导出目标 URI(如 "output.json"、"file:///tmp/x.csv"、"stdout:")。
// 支持 URI 模板占位符,详见 URIParams.Render。
URI string
// Format 导出格式。
Format Format
// Overwrite 为 true 时覆盖已有文件(仅对 FileStorage 有效)。
Overwrite bool
// StoreEmpty 为 true 时即使没有 Item 也会创建输出文件。
StoreEmpty bool
// Options 传递给 Exporter 的配置。
Options ExporterOptions
// Filter 决定 Item 是否应导出到此 Feed。为空时接受所有 Item。
Filter ItemFilterFunc
// Storage 可选地显式指定一个已构造的 FeedStorage。
// 若为 nil,FeedExport 会通过 NewStorageForURI(URI) 自动构造。
Storage FeedStorage
}
FeedConfig 描述一个 Feed 导出目标的全部配置。 对应 Scrapy 中 FEEDS 字典的一个条目(uri → options)。
type FeedSlot ¶
type FeedSlot struct {
// contains filtered or unexported fields
}
FeedSlot 组合一个 Storage 与一个 Exporter,代表一个正在进行中的 Feed 导出任务。 对应 Scrapy 的 FeedSlot。
生命周期:
- NewFeedSlot — 构造
- Start — 打开 Storage,启动 Exporter
- ExportItem — 反复调用
- Close — 结束 Exporter,提交 Storage
线程安全性:FeedExport 扩展通过监听同步信号 item_scraped 串行化调用, 因此 FeedSlot 本身无需加锁。为防御性考虑,核心方法上仍加 mutex。
func NewFeedSlot ¶
func NewFeedSlot(cfg FeedConfig, logger *slog.Logger) (*FeedSlot, error)
NewFeedSlot 构造一个 FeedSlot。 调用方必须在 Start 之前完成所有配置填充。
func (*FeedSlot) Close ¶
Close 结束 Exporter 并提交 Storage。 如果未曾写入任何 Item 且 StoreEmpty=false,则跳过(不创建空文件)。 即使处理过程中出错,也会尽力关闭所有资源。
func (*FeedSlot) ExportItem ¶
ExportItem 写入一个 Item。 如果 Filter 拒绝此 Item,直接返回 nil。 如果 FeedSlot 尚未启动,会自动启动(延迟启动,适配 StoreEmpty=false 场景)。
type FeedStorage ¶
type FeedStorage interface {
// Open 打开存储,返回一个可写入的 io.WriteCloser。
// sp 为当前 Spider,实现可根据 Spider 名称等信息定制路径。
// 返回的 WriteCloser 必须由调用方通过 Store 传回以便正确关闭。
Open(ctx context.Context, sp spider.Spider) (io.WriteCloser, error)
// Store 将 Open 返回的 writer 的内容持久化。
// 实现应负责关闭 writer(若尚未关闭)。
// 对于直接写文件的实现,Store 可能只是关闭句柄;
// 对于需要两阶段提交的实现(如临时文件 + rename),Store 会执行 rename。
Store(ctx context.Context, w io.WriteCloser) error
}
FeedStorage 定义导出存储后端接口。 对应 Scrapy 的 FeedStorageProtocol / IFeedStorage。
生命周期:
- Open — Spider 打开时调用,返回一个 io.WriteCloser 供 Exporter 写入。
- Store — Spider 关闭时调用,将 Open 返回的 writer 的内容持久化到最终位置。
典型实现场景:
- FileStorage : Open 直接打开目标文件,Store 关闭文件句柄即完成。
- StdoutStorage: Open 返回 os.Stdout 的包装,Store 空实现。
线程安全性:单个 FeedStorage 实例由单个 FeedSlot 独占使用,无需并发保护。
func NewStorageForURI ¶
func NewStorageForURI(uri string, overwrite bool) (FeedStorage, error)
NewStorageForURI 根据 URI 的 scheme 自动选择存储后端。 支持:
- "stdout:" 或 "-" → StdoutStorage
- "file://...", 相对/绝对路径 → FileStorage
不支持的 scheme(如 s3://、ftp://)会返回错误。
type FileStorage ¶
type FileStorage struct {
// contains filtered or unexported fields
}
FileStorage 将导出数据写入本地文件。 对应 Scrapy 的 FileFeedStorage。
特性:
- 支持 "file://" 前缀 URI 或普通路径
- 支持目录自动创建(父目录不存在时递归创建)
- overwrite=true 时覆盖已有文件,否则以追加模式打开
func NewFileStorage ¶
func NewFileStorage(uri string, overwrite bool) (*FileStorage, error)
NewFileStorage 根据 URI 创建一个本地文件存储。 URI 可以是:
- "output.json" — 相对路径
- "/tmp/output.json" — 绝对路径
- "file:///tmp/output.json" — 带 file:// scheme
func (*FileStorage) Open ¶
func (s *FileStorage) Open(ctx context.Context, sp spider.Spider) (io.WriteCloser, error)
Open 打开(或创建)目标文件,返回 io.WriteCloser。
func (*FileStorage) Store ¶
func (s *FileStorage) Store(ctx context.Context, w io.WriteCloser) error
Store 关闭文件句柄。由于 FileStorage 直接写入目标位置, 无需额外的 rename 操作。
type Format ¶
type Format string
Format 表示导出格式。
const ( // FormatJSON 表示 JSON 数组格式(所有 Item 被封装在一个 JSON 数组中)。 FormatJSON Format = "json" // FormatJSONLines 表示 JSON Lines 格式(每行一个 JSON 对象)。 // 也常被简写为 "jsonl" 或 "jl"。 FormatJSONLines Format = "jsonlines" // FormatCSV 表示 CSV 格式。 FormatCSV Format = "csv" // FormatXML 表示 XML 格式。 FormatXML Format = "xml" )
func NormalizeFormat ¶
NormalizeFormat 归一化格式名,兼容常见别名。 例如 "jl" / "jsonl" / "jsonlines" 都归一化为 FormatJSONLines。
Example ¶
ExampleNormalizeFormat 演示格式名归一化。
package main
import (
"fmt"
"github.com/dplcz/scrapy-go/pkg/feedexport"
)
func main() {
fmt.Println(feedexport.NormalizeFormat("json"))
fmt.Println(feedexport.NormalizeFormat("jl"))
fmt.Println(feedexport.NormalizeFormat("jsonl"))
fmt.Println(feedexport.NormalizeFormat("jsonlines"))
fmt.Println(feedexport.NormalizeFormat("csv"))
fmt.Println(feedexport.NormalizeFormat("xml"))
}
Output: json jsonlines jsonlines jsonlines csv xml
type ItemExporter ¶
type ItemExporter interface {
// StartExporting 开始导出过程,必须在调用 ExportItem 之前调用。
// 实现可以在此写入格式特定的前缀(如 JSON 的 "["、XML 的根元素开标签)。
StartExporting() error
// ExportItem 序列化一个 Item 并写入底层 writer。
ExportItem(item any) error
// FinishExporting 结束导出过程。
// 实现可以在此写入格式特定的后缀(如 JSON 的 "]"、XML 的根元素闭标签)并刷新缓冲。
FinishExporting() error
}
ItemExporter 定义 Item 序列化器接口。 对应 Scrapy 的 BaseItemExporter。
生命周期:
- StartExporting — 开始导出(如写入 JSON 数组的 "[")
- ExportItem — 反复调用,每次序列化一个 Item
- FinishExporting— 结束导出(如写入 JSON 数组的 "]")
实现应当是非并发安全的:同一个 Exporter 由单个 FeedSlot 独占使用, Feed Export 扩展通过信号串行化调用。
func NewExporter ¶
func NewExporter(format Format, w io.Writer, opts ExporterOptions) (ItemExporter, error)
NewExporter 根据格式构造一个 Exporter。 format 会被 NormalizeFormat 归一化后查找。
type ItemFilterFunc ¶
ItemFilterFunc 决定一个 Item 是否应该被导出到某个 Feed。 返回 true 表示接受,false 表示过滤。
对应 Scrapy 的 ItemFilter.accepts;此处采用函数类型而非接口, 以契合 Go 的函数式风格并避免不必要的类型层次。
type JSONExporter ¶
type JSONExporter struct {
// contains filtered or unexported fields
}
JSONExporter 将 Item 序列化为 JSON 数组。 对应 Scrapy 的 JsonItemExporter。
输出格式示例(indent = 0):
[{"a":1},{"b":2}]
输出格式示例(indent > 0):
[
{"a": 1},
{"b": 2}
]
注意:由于 JSON 数组需要在 Item 与 Item 之间插入 ",", 本实现会累积状态(firstItem 标记),因此必须按 Start → Export → Finish 顺序使用。
func NewJSONExporter ¶
func NewJSONExporter(w io.Writer, opts ExporterOptions) *JSONExporter
NewJSONExporter 创建一个 JSON 数组格式的 Exporter。
func (*JSONExporter) ExportItem ¶
func (e *JSONExporter) ExportItem(item any) error
ExportItem 写入一个 Item。 若 Item 与上一个 Item 之间需要分隔符,会自动写入 ","。
func (*JSONExporter) FinishExporting ¶
func (e *JSONExporter) FinishExporting() error
FinishExporting 写入 "]" 结束 JSON 数组。
func (*JSONExporter) StartExporting ¶
func (e *JSONExporter) StartExporting() error
StartExporting 写入 "[" 开始 JSON 数组。
type JSONLinesExporter ¶
type JSONLinesExporter struct {
// contains filtered or unexported fields
}
JSONLinesExporter 将 Item 序列化为 JSON Lines 格式(每行一个 JSON 对象)。 对应 Scrapy 的 JsonLinesItemExporter。
输出格式示例:
{"a":1}
{"b":2}
相比 JSONExporter,JSON Lines 格式天然流式友好:
- 每个 Item 独立,可以增量读取
- 不需要数组开闭标记,Item 之间无依赖
- 大数据集场景下更易于处理
func NewJSONLinesExporter ¶
func NewJSONLinesExporter(w io.Writer, opts ExporterOptions) *JSONLinesExporter
NewJSONLinesExporter 创建一个 JSON Lines 格式的 Exporter。
func (*JSONLinesExporter) ExportItem ¶
func (e *JSONLinesExporter) ExportItem(item any) error
ExportItem 序列化一个 Item 并追加 "\n"。
func (*JSONLinesExporter) FinishExporting ¶
func (e *JSONLinesExporter) FinishExporting() error
FinishExporting 标记导出结束。JSON Lines 无需写入后缀。
func (*JSONLinesExporter) StartExporting ¶
func (e *JSONLinesExporter) StartExporting() error
StartExporting 标记开始导出。JSON Lines 无需写入前缀。
type SerializeFunc ¶
SerializeFunc 定义字段序列化函数。 接收原始字段值,返回序列化后的值。
对应 Scrapy 中 Field(serializer=func) 的 serializer 参数。
func LookupSerializer ¶
func LookupSerializer(name string) (SerializeFunc, bool)
LookupSerializer 按名称查找已注册的序列化函数。 返回 nil 和 false 表示未注册。
type StdoutStorage ¶
type StdoutStorage struct{}
StdoutStorage 将导出数据直接写入 os.Stdout。 对应 Scrapy 的 StdoutFeedStorage。
特性:
- Open 返回一个对 os.Stdout 的 no-op wrapper(防止外部关闭 Stdout)
- Store 为 no-op
- overwrite 选项无意义,保留仅为接口一致性
func (*StdoutStorage) Open ¶
func (s *StdoutStorage) Open(ctx context.Context, sp spider.Spider) (io.WriteCloser, error)
Open 返回 Stdout 的安全包装。
func (*StdoutStorage) Store ¶
func (s *StdoutStorage) Store(ctx context.Context, w io.WriteCloser) error
Store no-op(Stdout 不需要后处理)。
type URIParams ¶
type URIParams struct {
// SpiderName 为 Spider.Name()
SpiderName string
// Time 为爬取开始的 UTC 时间,格式化字符串 "YYYY-MM-DDTHH-MM-SS"
Time string
// BatchTime 等同于 Time(保留兼容性)
BatchTime string
// BatchID 批次 ID,从 1 开始,当前 Go 版本不支持分批,恒为 1
BatchID int
// Extra 是用户自定义的额外变量
Extra map[string]string
}
URIParams 存储可用于 URI 模板的变量。 对应 Scrapy 的 _get_uri_params 函数返回值。
type XMLExporter ¶
type XMLExporter struct {
// contains filtered or unexported fields
}
XMLExporter 将 Item 序列化为 XML 格式。 对应 Scrapy 的 XmlItemExporter。
输出格式示例(RootElement=items, ItemElement=item):
<?xml version="1.0" encoding="utf-8"?>
<items>
<item>
<name>Foo</name>
<price>10</price>
</item>
</items>
字段值支持以下递归规则:
- 切片/数组:每个元素展开为 <value>…</value> 子节点
- map:每个键值对展开为 <key>value</key> 子节点
- 其他类型:使用 fmt.Sprint 转为文本
func NewXMLExporter ¶
func NewXMLExporter(w io.Writer, opts ExporterOptions) *XMLExporter
NewXMLExporter 创建一个 XML 格式的 Exporter。
func (*XMLExporter) ExportItem ¶
func (e *XMLExporter) ExportItem(item any) error
ExportItem 写入一个 Item 元素。
func (*XMLExporter) FinishExporting ¶
func (e *XMLExporter) FinishExporting() error
FinishExporting 写入根元素闭标签并刷新缓冲。
func (*XMLExporter) StartExporting ¶
func (e *XMLExporter) StartExporting() error
StartExporting 写入 XML 声明和根元素开标签。