ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Pholcus 爬虫规则实战:用 Go 静态规则采集搜房二手房列表

Pholcus 爬虫规则实战:用 Go 静态规则采集搜房二手房列表
  • 后端

【免费下载链接】pholcus

Pholcus is a distributed high-concurrency crawler software written in pure golang

项目地址:https://gitcode.com/gh_mirrors/ph/pholcus
点击查看免费下载

本篇技术指南以 pholcus 仓库内置的搜房(fang.com)二手房列表爬虫规则 readme.md 及其完整实现 fang_resell_list.go 为主线,拆解一个真实可运行的静态爬虫规则:从Spider结构定义、RuleTree分页入口、goqueryDOM 解析,到字段清洗与Output结果回传的完整调用链。读完本文,你将掌握 pholcus 静态规则的标准写法、如何按页码批量入队、如何在解析函数里用字符串与 DOM 操作抽取结构化字段,以及当"页数过多导致蜘蛛崩溃"时可以从源码的哪些 panic 防护机制入手排查。

规则定位:一次只爬一页,字段结构清晰

搜房二手房列表规则是 pholcus 内置静态规则库 sample/static_rules 中的一员,目标站点为http://esf.zz.fang.com/house/i31/(郑州搜房二手房列表页)。根据 readme.md 的说明,它仅爬取列表页、一次爬取一页,不做详情页深入,属于典型的"列表采集 + 结构化字段输出"入门级规则。

规则的字段设计(文档声明的 11 个字段,外加源码实现中补充的 2 个),在 ItemFields 中按输出顺序定义如下:

字段名含义来源说明
communityName小区名.info p.mt10 a span文本
totalFloor总层数从(共N层)字符串中剥离
rooms房间数从N室M厅中拆分
halls厅数量从N室M厅中拆分
buildTime建筑年代|建筑年代:前缀剥离
address地址|拆分后半段
direction朝向|向后缀剥离
area面积(㎡)剥离㎡单位
price总价(万)剥离万单位
unitPrice单价(元/㎡)剥离元/㎡单位
locationType所在层数高低从低层|共33层中提取
remoteId搜房房源 id从详情链接_id.htm提取(源码补充)
business商圈从地址商圈-小区拆分(源码补充)

相比原文档,实际代码多出remoteId(房源唯一标识,可用于后续增量去重)与business(商圈)两个字段,说明规则在维护中做过字段扩展。

规则骨架:Spider 结构与注册机制

整个规则是一个包级变量fangList,类型为*spider.Spider,在init()中调用fangList.Register()完成注册(fang_resell_list.go)。Spider结构体定义在 app/spider/spider.go,规则中使用到的关键字段:

  • Name:蜘蛛名称"resell house of fang.com",在规则库中必须唯一;注册时若重名,Species.Add 会自动追加数字后缀。
  • Description:任务描述,同时会在命令行蜘蛛列表、Web 界面中展示。
  • EnableCookie: true:请求携带 Cookie,适合目标站点要求会话保持的场景;该开关会通过 AddQueue 自动透传给每个请求。
  • RuleTree:规则树,包含Root(任务入口)和Trunk(规则节点映射),是 pholcus 静态规则的核心组织方式。

注册后,规则会进入全局单例spider.Species列表。首次访问时 Get() 还会按拼音首字母对蜘蛛排序,因此命令行界面里搜房规则的序号由该排序结果决定,选择时以-h帮助输出的实际序号为准。

分页入口:Root 函数与 URL 构造

RuleTree.Root是任务的起始点(fang_resell_list.go):

Root: func(ctx *spider.Context) { var i = 1 //for i = 1; i < 101; i++ { ctx.AddQueue(&request.Request{ URL: "http://esf.zz.fang.com/house/i3" + strconv.Itoa(i) + "/", Rule: "fang_collection", Temp: map[string]interface{}{"p": 1}, }) //} },

要点解读:

  1. URL 分页规律:搜房列表页 URL 形如house/i31/、house/i32/……即页码拼接在i3之后。当前默认i=1只抓首页,即文档所述"一次爬取一页"。
  2. 打开多页爬取:文档第 2 条提到的"修改 37 行打开多页爬取",对应的正是把var i = 1上方的注释//for i = 1; i < 101; i++ {取消注释,并在ctx.AddQueue(...)之后补上右大括号},即可循环入队第 1~100 页。
  3. Temp传递临时数据:这里写入{"p": 1},配合分页循环可用于后续解析中标记页码(当前解析函数未使用,但这是 pholcus 规则间传参的标准通道)。request.Request的完整可选字段(Method、DialTimeout、TryTimes、RedirectTimes、DownloaderID等)及其默认值见 app/downloader/request/request.go,未显式设置时由 Prepare() 自动补齐默认值(如TryTimes默认 3 次重试、DialTimeout默认 2 分钟)。

ctx.AddQueue内部会校验URL与Rule两个必填项,自动设置蜘蛛名与 Cookie 开关,完成 Prepare 后把请求推入调度矩阵(spider.RequestPush),随后由爬虫引擎按并发配置从矩阵中拉取执行。

页面解析:goquery 选择器与字符串清洗

请求下载成功后,响应被交给Trunk中名为fang_collection的规则节点执行ParseFunc(fang_resell_list.go)。整体流程是:定位列表容器 → 逐条房源遍历 → DOM 取文本 → 字符串拆分清洗 → Output 输出。

列表遍历

ctx.GetDom().Find(".houseList dl").Each(func(i int, s *goquery.Selection) { ... })

ctx.GetDom()返回的是 pholcus 内置的 goquery 文档对象(基于 CSS 选择器),Find(".houseList dl")选中每一条房源条目,Each逐条回调。DOM 是惰性初始化的:首次调用时由 GetDom() 从响应体解析,内部会先经initText按页面charset做编码探测与 UTF-8 转换(app/spider/context.go),对中文站点尤其重要。

字段抽取的三种技巧

  • 直接取文本:小区名、地址直接s.Find(".info p.mt10 a span").Text()。
  • 链接提取 id:s.Find("dd.info p.title a").Attr("href")返回一个Option类型,用IsSome()判断存在后用Unwrap()取出,再按_与.htm拆分得到remoteId。
  • 正则式字符串拆分:这是本规则最有代表性的部分。房源信息行的原始文本形如3室2厅 | 低层|共33层 | 南向 | 建筑年代:2015年,代码用strings.Fields按空白切分为 4 段,再逐一清洗:
roomLine := strings.Fields(roomLineTmp) if len(roomLine) == 4 { roomsTmp := roomLine[0] // "3室2厅" roomsTmp = strings.Replace(roomsTmp, "厅", "", 1) roomsS := strings.Split(roomsTmp, "室") // ["3", "2"] rooms, halls = roomsS[0], roomsS[1] buildingTmpSec := strings.Split(roomLine[1], "(共") // ["低层|", "33层)"] locationType = strings.Replace(buildingTmpSec[0], "|", "", 1) totalFloor = strings.Replace(buildingTmpSec[1], "层)", "", 1) buildTime = strings.Replace(roomLine[3], "|建筑年代:", "", 1) direction = strings.Replace(roomLine[2], "|", "", 1) direction = strings.Replace(direction, "向", "", 1) }

地址同样利用strings.Split(address, "-")把"商圈-小区"拆成商圈与地址两段,实现一个选择器喂出两个字段。

结果回传:map[int] 索引与 ItemFields 的映射

解析完成后调用:

ctx.Output(map[int]interface{}{ 0: strings.Trim(communityName, " "), 1: strings.Trim(totalFloor, " "), // ... 依次对应 ItemFields 下标 12: strings.Trim(business, " "), })

这里的关键机制在 Context.CreateItem:当传入map[int]interface{}时,pholcus 会按当前规则ItemFields的下标顺序将整数键映射为字段名——即0 → communityName、1 → totalFloor……12 → business。因此ItemFields的书写顺序必须与Output的整数键严格对应,这是静态规则中容易踩坑、但效率最高(无需重复写字段名)的输出方式。

Output生成的数据单元DataCell默认还会附带Url、ParentUrl、DownloadTime三个默认字段(除非蜘蛛设置NotDefaultField: true),见 app/spider/context.go。解析后的条目经爬虫引擎 crawler.Process 统一拉取并送入 Pipeline,最终落盘。

如何运行这条规则

规则已通过_ "github.com/andeya/pholcus/sample/static_rules"批量导入(pholcus_rules.go 中可见fang_resell_list的注册项),随 sample/main.go 一同编译:

go build -o pholcus ./sample/ ./pholcus -h # 查看全部参数与蜘蛛序号

采用 Cmd 模式运行时,需通过-c_spider传入蜘蛛在列表中的序号(多蜘蛛以,分隔,传*表示全部),示例(cmd/pholcus-cmd.go 与 README.md):

pholcus -_ui=cmd -a_mode=0 -c_spider=<搜房规则序号> -a_outtype=csv \ -a_thread=20 -a_batchcap=5000 -a_pause=300 -a_proxyminute=0 \ -a_keyins="<pholcus><golang>" -a_limit=10 -a_success=true -a_failure=true

常用参数说明:

参数作用
-_ui操作界面:web/gui(仅 Windows)/cmd
-a_mode运行模式:0单机、服务端、客户端(分布式)
-c_spider选择蜘蛛序号(cmd 模式专有)
-a_thread并发协程数,范围[1~99999]
-a_pause平均暂停毫秒数,>=100,实际按 50%~200% 随机化
-a_limit采集上限(默认限制 URL 数),>=0
-a_outtype输出方式,如csv、mgo、mysql、kafka等
-a_batchcap分批输出容量[1~5000000]
-a_proxyminute代理 IP 更换频率(/分钟),为 0 不使用代理

CSV 输出时(output_csv.go),文件会写入 UTF-8 BOM,表头即规则ItemFields的字段名,并追加"当前链接、上级链接、下载时间"三个默认列,输出目录由 config.ini 的textoutdir(默认pholcus_pkg/text_out)与任务开始时间共同决定。规则的subNamespace默认取RuleName(即fang_collection),因此多批次结果会按蜘蛛名__fang_collection的目录层级区分。

关于"页数过多导致蜘蛛崩溃"的源码级排查线索

readme 第 3 条记录了已知问题:"如果爬取的页面数太多会导致蜘蛛崩溃,原因未知,待查"。原文并未给出结论,本文不臆测根因,仅从当前仓库源码的防护与恢复机制出发,给出可验证的排查方向:

  1. Root 入口的 panic 防护:spider.Start()对RuleTree.Root的调用包了defer/recover,只会记录Panic [root]日志而不会终止整个进程(app/spider/spider.go)。
  2. 请求处理链的 panic 防护:crawler.Process对下载、解析全程有defer/recover,崩溃时会记录堆栈并调用DoHistory(req, false)将该请求记入失败队列(app/crawler/crawler.go)。
  3. 可重点观察的侧面:批量入队第 1~100 页时,若未对-a_limit或调度矩阵做总量控制,瞬间入队的大量请求会全部进入调度矩阵等待拉取;结合-a_thread并发与-a_pause限速调小后复现,同时打开日志文件pholcus_pkg/logs/pholcus.log(路径定义见 config/config.go)检索Panic关键字,是定位"崩溃"发生在入队阶段还是下载/解析阶段的最直接手段。

由于原文档标注"原因未知,待查",以上仅为基于现有代码结构的排查建议,不作为崩溃结论。

小结与扩展方向

搜房二手房规则是一份典型的 pholcus 静态爬虫模板,麻雀虽小但五脏俱全:Spider注册、RuleTree分页入口、AddQueue请求入队、goqueryDOM 解析、字符串清洗、map[int]索引式Output回传,全部在 fang_resell_list.go 的 130 余行内闭环。基于它可以自然扩展的能力包括:

  • 取消for循环注释实现多页抓取,并用-a_limit控制总量、用-a_pause控制频率;
  • 利用已提取的remoteId配合-a_success/-a_failure的历史记录机制实现增量去重;
  • 参照 sample/static_rules/README.md 的维护规范,为规则补充AidFunc、Namespace/SubNamespace或切换mgo/mysql等输出后端。

如果想要对照更多解析技巧,仓库内 jdsearch、baidusearch 等规则与 app/spider/context.go 中的ContextAPI 注释(如Aid、SetTemp、SetTimer、JsAddQueue等)都是很好的参考资料。

  • 后端

【免费下载链接】pholcus

Pholcus is a distributed high-concurrency crawler software written in pure golang

项目地址:https://gitcode.com/gh_mirrors/ph/pholcus
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进