在做独立开发或产品运营时,最难的事情往往不是写代码,而是找到第一批精准用户。你经常要在各种论坛、社区、评论区蹲守,希望在“需要你产品的人”出现的瞬间能发现他。最近看到一个 Show HN 项目很有意思:它做了一件事,用程序自动去公开平台捕捉“正在寻找你所卖产品的人”,把人工逛帖子的体力活交给脚本去完成。
这篇文章就围绕“如何从零构建一个这样的销售线索发现工具”展开,我会用 Python 实现一个完整可运行的版本。它可以从公开 API 或本地 HTML 文件中采集帖子文本,通过关键词、正则和评分机制判断用户是否存在购买意图,最后输出成 CSV 或 JSON 格式的线索列表。
读完这篇文章,你能掌握:
销售线索发现工具的核心思路。
如何设计数据采集层、意图过滤层和结果输出层。
如何使用 requests + BeautifulSoup 处理公开页面。
如何用关键词和正则做初步需求识别。
如何结合业务关键词对线索评分。
常见踩坑点和工程化建议。
需要说明的是:这个工具只适合采集公开可访问的数据,并且必须遵守目标平台的条款与隐私要求。不要绕过登录、验证码,也不要采集非公开内容。合理使用,它才能成为一个合规的“客户发现助手”。
1. 背景与核心概念
1.1 这个工具解决什么问题
很多自由职业者、SaaS 开发者、代理商都会遇到一个场景:你提供一个服务,比如“小程序开发”“API 集成”“数据分析报表”,但不知道谁现在正好需要它。
传统的获客方式有两种:
主动找客户:去行业论坛、微信群、LinkedIn 搜索相关需求,手动记录联系方式。
被动等客户:发内容、投广告,等用户找你。
第一种方式非常耗时,第二种方式成本又高。而这个工具的目标是做一个“自动搜猎器”,定期扫描公开社区中带有需求信号的帖子,把可能对你有价值的对话提取出来,放到一个统一文件里,由你决定是否回复。
它本质上是把下面这条人工路径自动化:
TEXT
复制
1
发现帖子 -> 阅读内容 -> 判断是否与你产品相关 -> 提取链接 -> 记录到表格
变成:
TEXT
复制
1
采集帖子 -> 过滤需求意图 -> 匹配业务关键词 -> 评分排序 -> 输出线索清单
1.2 什么是“需求信号”
需求信号(Buying Intent Signal)是用户内容中暴露出的“想买东西”或“正在找解决方案”的痕迹。
举例来说:
I need someone to build a chatbot for my website
Any recommendation for a data visualization tool?
Looking for a developer who knows Python and AWS
哪位朋友可以帮忙做一个爬虫?价格私聊
这些句子通常带有比较明显的意图词,比如:
looking for
need
recommend
help with
find someone
求推荐
找人做
有偿帮忙
但只有意图词还不够,还需要业务关键词,否则你搜到的大部分内容是无关的。比如你卖“API 集成服务”,理想的候选句子是:
Need an API to integrate Stripe payments
这里的 API 是业务词,Need 是意图词,两者同时出现,才值得作为线索。
1.3 为什么用 Python 实现这种工具
选 Python 有几个直接原因:
requests 可以快速请求公开接口或页面。
BeautifulSoup 和 lxml 可以解析 HTML。
正则和原生字符串方法让关键词匹配非常简单。
pandas 可以方便导出 Excel,但为了减少依赖,也可以用 csv 模块。
生态里有现成的调度工具(APScheduler、cron 等),非常适合做定时任务。
这个项目不需要高并发,也不需要大规模爬取,重点是代码清晰、逻辑易改、能快速对接新数据源。Python 正好满足这些要求。
2. 环境准备与版本说明
2.1 运行环境
本文示例在以下环境中验证:
操作系统:Windows 10 / macOS / Linux 均可
Python:3.9 及以上
包管理工具:pip
终端或命令行工具
版本需要根据你的项目实际情况调整,本文示例以常见环境为例,重点演示配置思路。
如果你当前没有 Python 环境,建议先安装 Python 3.10+,并且创建一个独立虚拟环境,避免依赖冲突。
BASH
复制
2
source venv/bin/activate
2.2 依赖库说明
本项目只用到三个第三方库:
库名
用途
requests
请求公开 API 或网页
beautifulsoup4
解析 HTML 内容
lxml
BeautifulSoup 的解析引擎,速度更快
在项目根目录创建 requirements.txt:
安装依赖:
BASH
复制
1
pip install -r requirements.txt
如果你只需要本地 HTML 示例,连网络请求都不需要,requests 也可以省掉。
2.3 项目整体设计
我设计了一个分层结构,方便以后扩展更多数据源。
TEXT
复制
7
│ ├── local_collector.py
8
│ └── hackernews_collector.py
11
│ └── intent_filter.py
各模块职责:
文件
职责
config.py
存放关键词、意图词、源配置
collectors/
数据采集层,负责从不同来源获取原始文本
filters/intent_filter.py
意图识别与评分,负责判断帖子是否值得记录
find_buyers.py
主流程,串联采集、过滤、输出
sample.html
本地测试页面,用于无网络运行
3. 核心模块拆解
3.1 数据采集层
数据采集层是整个工具的地基。它要解决三个问题:
从哪拿数据?
拿到的数据长什么样?
怎么统一交给上层过滤模块?
为了适配不同数据源,我定义了一个简单的抽象接口:每个采集器都返回一个列表,列表里每一项都是字典,至少包含 title、content 和 url 三个字段。
PYTHON
复制
3
"title" : "Looking for an API developer" ,
4
"content" : "Need help integrating payment APIs..." ,
5
"url" : "https://example.com/post/123"
之所以增加 title 和 content 分开的结构,是因为评分时标题权重往往高于正文,分开字段可以灵活调整。
常见的采集方式有三种:
官方公开 API:比如 Hacker News 的 Algolia API,适合拿到结构化数据。
页面解析:用 BeautifulSoup 解析公开页面,适合没有 API 的论坛或问答站。
本地文件:把网页保存成 HTML 文件后解析,适合调试和离线运行。
3.2 意图过滤层
过滤层负责判断一条内容是否包含“购买意图”。
我用两层判断:
第一层是“硬条件”:正文中必须出现意图词,并且必须出现至少一个业务关键词。第二层是“评分”:通过标题是否命中、意图词频率、关键词距离等维度给线索打分。
为什么不直接用单一关键词?
因为意图词和业务关键词单独出现都有噪音。比如搜索 looking for 会得到大量无关内容,比如 I am looking for my keys;单独搜索 API 又会得到大量技术教程。
所以核心设计是“意图词 + 业务关键词”的组合判断,这是一个非常实用的过滤思路。
3.3 结果评分与输出层
结果输出不是简单罗列,而是要排序。对销售线索来说,最值得回复的帖子应该是:
标题就提到需求。
正文长度适中。
意图词出现次数较多。
业务关键词靠近意图词。
评分逻辑可以设计得尽量简单,不必上机器学习。先给硬规则,再加权重,效果已经不错。
输出格式至少包含:
来源 URL
标题
正文摘要
匹配到的意图词
匹配到的业务关键词
综合得分
4. 完整实战案例
下面我们开始从零构建这个工具。为了同时照顾离线运行和真实场景,我会实现两个采集器:
LocalCollector:读取本地 sample.html,离线可用。
HackerNewsCollector:调用 Hacker News 公开搜索 API,获取真实帖子。
4.1 创建项目结构
先创建目录:
BASH
复制
3
mkdir collectors filters output
4
touch config.py find_buyers.py requirements.txt
5
touch collectors/__init__.py collectors/local_collector.py collectors/hackernews_collector.py
6
touch filters/__init__.py filters/intent_filter.py
最终目录:
TEXT
复制
7
│ ├── local_collector.py
8
│ └── hackernews_collector.py
11
│ └── intent_filter.py
4.2 添加依赖配置
创建 requirements.txt:
安装:
BASH
复制
1
pip install -r requirements.txt
4.3 编写配置模块
这个工具比较核心的配置是“业务关键词”和“意图词”。我放到 config.py 中,方便你按自己的业务调整。
PYTHON
复制
28
TITLE_MATCH_BONUS = 20
37
OUTPUT_JSON = "output/leads.json"
38
OUTPUT_CSV = "output/leads.csv"
41
HN_SEARCH_URL = "https://hn.algolia.com/api/v1/search_by_date"
42
HN_QUERY_TAGS = "ask_hn"
43
HN_RESULTS_PER_PAGE = 20
这里我使用的英文关键词适合匹配 Hacker News 这类英文社区。如果你要扫描中文社区,把关键词换成“求推荐”“找人做”“有偿求助”即可。
4.4 实现本地 HTML 采集器
为了解决没有网络时也能测试的问题,我创建一个本地 HTML 文件 sample.html,模拟一个帖子列表页面。
HTML
复制
4
<head > <meta charset ="utf-8" > <title > Sample Forum</title > </head >
7
<h2 class ="post-title" > Looking for help with API integration</h2 >
8
<p class ="post-content" > We need a developer who can help us connect our CRM with the payment API. Please share recommendations.</p >
9
<a class ="post-url" href ="https://example.com/post/1" > https://example.com/post/1</a >
12
<h2 class ="post-title" > Show HN: My new dashboard</h2 >
13
<p class ="post-content" > I built a dashboard with Python and API data. Check it out!</p >
14
<a class ="post-url" href ="https://example.com/post/2" > https://example.com/post/2</a >
17
<h2 class ="post-title" > Find someone to build a website</h2 >
18
<p class ="post-content" > Looking for a developer to build a static website for our small business. Need it quick.</p >
19
<a class ="post-url" href ="https://example.com/post/3" > https://example.com/post/3</a >
然后实现 LocalCollector。
PYTHON
复制
2
from bs4 import BeautifulSoup
8
适合离线测试,也适合处理你手动保存的目标页面。
11
def __init__ (self, html_file ):
12
self.html_file = html_file
15
with open (self.html_file, "r" , encoding="utf-8" ) as f:
18
soup = BeautifulSoup(html, "lxml" )
21
for item in soup.select(".post" ):
22
title_tag = item.select_one(".post-title" )
23
content_tag = item.select_one(".post-content" )
24
url_tag = item.select_one(".post-url" )
26
if not title_tag or not content_tag or not url_tag:
31
"title" : title_tag.get_text(strip=True ),
32
"content" : content_tag.get_text(strip=True ),
33
"url" : url_tag.get_text(strip=True ),
这个采集器做的事情很简单:读取 HTML,找到所有 .post 节点,把标题、正文、链接提取成字典列表。
4.5 实现 Hacker News API 采集器
Hacker News 提供了公开的 Algolia API,非常适合作为真实数据源。它不需要 API Key,但要求调用频率不能太高。
PYTHON
复制
3
from bs4 import BeautifulSoup
5
from config import HN_SEARCH_URL, HN_QUERY_TAGS, HN_RESULTS_PER_PAGE
8
class HackerNewsCollector :
9
"""从 Hacker News 搜索公开帖子。"""
11
def __init__ (self, query, tags=None ):
13
self.tags = tags or HN_QUERY_TAGS
19
"hitsPerPage" : HN_RESULTS_PER_PAGE,
23
resp = requests.get(HN_SEARCH_URL, params=params, timeout=15 )
24
resp.raise_for_status()
26
except requests.RequestException as e:
27
print (f"[HackerNewsCollector] 请求失败: {e} " )
31
for hit in data.get("hits" , []):
33
title = hit.get("title" ) or ""
34
story_text = hit.get("story_text" ) or ""
35
url = hit.get("url" ) or f"https://news.ycombinator.com/item?id={hit.get('objectID' , '' )} "
39
story_text = BeautifulSoup(story_text, "lxml" ).get_text(strip=True )
44
"content" : story_text,
这个采集器有几个细节要注意:
Hacker News 的 story_text 可能是 HTML 格式,所以用 BeautifulSoup 做一次纯文本提取。
title 在部分 Ask 帖中为空,此时可以用 story_text 的开头作为标题。
不要对每个帖子都发请求,一定要一次 API 返回一批数据。
4.6 实现意图判断与评分
这是整个工具最核心的部分。我把它放到 filters/intent_filter.py。
PYTHON
复制
14
"""判断帖子是否包含购买意图,并计算线索得分。"""
16
def __init__ (self, business_keywords=None , intent_keywords=None ):
17
self.business_keywords = business_keywords or BUSINESS_KEYWORDS
18
self.intent_keywords = intent_keywords or INTENT_KEYWORDS
20
def _match_keywords (self, text, keywords ):
22
text_lower = text.lower()
26
pattern = r"\b" + re.escape(kw.lower()) + r"\b"
27
if re.search(pattern, text_lower):
31
def filter_and_score (self, post ):
32
"""根据标题和内容判断帖子是否为潜在线索。
34
返回 None 表示不匹配,否则返回带分数的副本。
36
title = post.get("title" , "" )
37
content = post.get("content" , "" )
38
combined_text = f"{title} . {content} " .lower()
40
matched_intents = self._match_keywords(combined_text, self.intent_keywords)
41
matched_business = self._match_keywords(combined_text, self.business_keywords)
44
if not matched_intents or not matched_business:
49
score += len (matched_intents) * INTENT_SCORE
50
score += len (matched_business) * BUSINESS_SCORE
52
title_text = title.lower()
53
for kw in matched_intents:
54
if kw.lower() in title_text:
55
score += TITLE_MATCH_BONUS
60
result["matched_intents" ] = matched_intents
61
result["matched_business" ] = matched_business
62
result["score" ] = score
这里的 _match_keywords 使用 \b 正则边界,好处是匹配 api 时不会把 landscape 误认为命中,也不会把 apid 命中。你如果要做中文关键词,则不需要 \b,直接 if kw in text_lower 即可。
4.7 实现主流程
现在写主程序 find_buyers.py,把采集、过滤、输出串起来。
PYTHON
复制
7
from collectors.local_collector import LocalCollector
8
from collectors.hackernews_collector import HackerNewsCollector
9
from filters.intent_filter import IntentFilter
10
from config import OUTPUT_CSV, OUTPUT_JSON
13
def save_json (data, path ):
14
os.makedirs(os.path.dirname(path), exist_ok=True )
15
with open (path, "w" , encoding="utf-8" ) as f:
16
json.dump(data, f, ensure_ascii=False , indent=2 )
19
def save_csv (data, path ):
20
os.makedirs(os.path.dirname(path), exist_ok=True )
22
print ("[save_csv] 没有数据,跳过 CSV 输出。" )
26
with open (path, "w" , encoding="utf-8-sig" , newline="" ) as f:
27
writer = csv.DictWriter(f, fieldnames=keys)
29
writer.writerows(data)
33
parser = argparse.ArgumentParser(description="销售线索发现工具" )
34
parser.add_argument("--source" , choices=["local" , "hn" ], default="local" , help ="数据源" )
35
parser.add_argument("--query" , default="looking for" , help ="搜索关键词,仅 hn 源使用" )
36
parser.add_argument("--html" , default="sample.html" , help ="本地 HTML 文件路径" )
37
args = parser.parse_args()
40
if args.source == "local" :
41
print (f"[main] 开始解析本地文件: {args.html} " )
42
collector = LocalCollector(args.html)
43
posts = collector.fetch()
45
print (f"[main] 开始从 Hacker News 搜索: {args.query} " )
46
collector = HackerNewsCollector(query=args.query)
47
posts = collector.fetch()
49
print (f"[main] 采集到 {len (posts)} 条内容" )
52
filter_engine = IntentFilter()
55
result = filter_engine.filter_and_score(post)
60
leads.sort(key=lambda x: x["score" ], reverse=True )
63
save_json(leads, OUTPUT_JSON)
64
save_csv(leads, OUTPUT_CSV)
66
print (f"[main] 过滤得到 {len (leads)} 条潜在线索" )
67
print (f"[main] JSON 输出: {OUTPUT_JSON} " )
68
print (f"[main] CSV 输出: {OUTPUT_CSV} " )
71
if __name__ == "__main__" :
这里使用了 utf-8-sig 编码保存 CSV,这样用 Excel 打开中文内容时不容易乱码。
4.8 运行与验证
先测试本地 HTML 数据源。在项目根目录执行:
BASH
复制
1
python find_buyers.py --source local --html sample.html
预期输出:
TEXT
复制
1
[main] 开始解析本地文件: sample.html
4
[main] JSON 输出: output/leads.json
5
[main] CSV 输出: output/leads.csv
生成的 output/leads.json 大致如下:
JSON
复制
3
"title" : "Looking for help with API integration" ,
4
"content" : "We need a developer who can help us connect our CRM with the payment API. Please share recommendations." ,
5
"url" : "https://example.com/post/1" ,
18
"title" : "Find someone to build a website" ,
19
"content" : "Looking for a developer to build a static website for our small business. Need it quick." ,
20
"url" : "https://example.com/post/3" ,
第二个帖子得分更高,因为它在标题里就出现了意图词,并且业务关键词更多。
如果你想测试真实数据源,可以运行:
BASH
复制
1
python find_buyers.py --source hn --query "looking for developer"
此时会去 Hacker News 搜索帖子,再把过滤结果写入 output 目录。
5. 常见问题与排查思路
这类工具在实际使用中会遇到不少问题。下面按错误现象、原因、解决方案整理一张排查表。
问题现象
常见原因
解决思路
ModuleNotFoundError: No module named 'bs4'
没有安装 beautifulsoup4
执行 pip install -r requirements.txt
本地 HTML 解析不出内容
选择器与 HTML 结构不匹配
打开 HTML 确认 class 名称,修改 .post 等选择器
网络请求超时
目标平台访问不稳定
增大 timeout,或增加重试机制
API 返回空列表
查询条件过于严格
减少关键词,检查 tags 参数是否正确
Hacker News 返回 429
请求频率过高
在请求之间增加 time.sleep(2),降低并发
CSV 中文乱码
编码不是 UTF-8 带 BOM
使用 encoding="utf-8-sig" 写入
误报太多
意图词太宽泛
增加业务关键词数量,或使用更严格的意图词
漏掉优质线索
业务关键词覆盖不全
扩展同义词,比如 developer 和 programmer
关键词匹配了错误内容
短词被长词包含
英文词使用 \b 正则边界,中文词改用 in 判断
重复帖子上次已经处理
没有去重
在输出时按 URL 去重,或记录已处理 ID
5.1 为什么请求频率不能太高
公开 API 通常都有限流策略。以 Hacker News Algolia API 为例,虽然不需要 API Key,但同一个 IP 短时间内大量请求还是可能被拒绝。比较好的做法是:
每次运行只拉取一页或两页数据。
如果要做历史回扫,控制请求间隔在 1 秒以上。
把日志输出到文件,方便定位限流问题。
5.2 中文关键词匹配的注意点
本文演示的配置主要以英文为主。如果你要匹配中文内容,建议修改 IntentFilter 中的 _match_keywords,去掉 \b 边界逻辑,因为中文没有天然分词边界。
例如:
PYTHON
复制
1
def _match_keywords (self, text, keywords ):
2
text_lower = text.lower()
5
if kw.lower() in text_lower:
同时把 config.py 中的关键词替换成中文,比如:
PYTHON
复制
1
BUSINESS_KEYWORDS = ["小程序开发" , "网站制作" , "爬虫" , "API接口" ]
2
INTENT_KEYWORDS = ["求推荐" , "找人做" , "需要开发" , "有偿帮忙" , "急求" ]
6. 最佳实践与工程建议
有了一个能跑的脚本之后,下一步就是让它能在业务中稳定迭代。
6.1 合规与平台规则
这类工具本质上是在“采集公开内容并分析”。但“公开可访问”不等于“可以做任何事”。在工程化之前,先确认以下几点:
是否遵守目标平台的 API 使用条款?
是否明确标注数据来源?
是否存储了可识别的个人信息?存储期限和用途是什么?
是否会被平台视为爬虫行为而封禁?
我的建议是:优先使用官方 API,不要尝试绕过限制;如果只是个人使用,控制请求频次;如果是商业工具,应该接入正规数据服务或获得授权。
6.2 API 限流与重试
给网络请求增加重试是好习惯。可以用 requests 的 Session,配合简单的重试逻辑。
PYTHON
复制
4
def request_with_retry (session, url, params, retries=3 , timeout=15 ):
5
for attempt in range (retries):
7
resp = session.get(url, params=params, timeout=timeout)
8
resp.raise_for_status()
10
except requests.RequestException as e:
11
print (f"请求失败,第 {attempt + 1 } 次重试: {e} " )
12
time.sleep(2 * (attempt + 1 ))
6.3 去重与增量更新
线索工具很容易跑出重复结果。建议把已经输出过的 URL 保存到一个 processed_urls.txt 文件里,每次运行都跳过已经处理过的 URL。
PYTHON
复制
1
def load_processed_urls (path ):
2
if not os.path.exists(path):
4
with open (path, "r" , encoding="utf-8" ) as f:
5
return set (line.strip() for line in f if line.strip())
8
def save_processed_urls (path, urls ):
9
with open (path, "a" , encoding="utf-8" ) as f:
6.4 定时调度与增量更新
在开发环境跑一次没问题,生产环境建议做成定时任务。
Linux / macOS 使用 crontab:
BASH
复制
1
0 */6 * * * cd /path/to/find_buyers && /usr/bin/python3 find_buyers.py --source hn --query "looking for developer" >> logs/run.log 2>&1
Windows 可以使用“任务计划程序”。核心思想是每 6 小时跑一次,把结果追加到历史记录中。
6.5 如何扩展为语义识别
关键词规则虽然有效,但无法理解复杂表达。比如用户写:
I’m stuck with my payment integration
Does anyone know how to handle OAuth tokens?
这些句子没有直接出现 looking for,但明显是在求助。如果业务判断需要更准确,可以考虑接入 LLM 做二次判断。
思路是把候选内容交给大模型,请它判断是否包含购买意图。但要注意:
不要把所有原始数据都直接送到外部 API,避免隐私风险。
先做规则过滤,缩小候选集,再调用 LLM。
对 LLM 输出要做格式校验,避免不稳定。
这部分可以作为进阶方向。本文不演示具体代码,因为不同 LLM 服务的接口差异较大,而且需要额外付费。
6.6 日志与可观测性
工程化工具一定要有日志。简单做法是用 Python 的 logging 代替 print。
PYTHON
复制
5
format ="%(asctime)s [%(levelname)s] %(message)s" ,
7
logging.FileHandler("logs/tool.log" , encoding="utf-8" ),
8
logging.StreamHandler()
这样每次运行都会留痕,出现问题时可以直接看日志定位。
7. 总结与学习路线
这篇文章从一个简单的想法开始:我们能不能用程序自动发现“正在寻找你产品的人”?然后拆解了它的核心模块:数据采集、意图识别、评分排序、结果输出。
我们实现了一个可运行的 Python 工具,支持本地 HTML 和 Hacker News API 两个数据源。它的核心设计是“业务关键词 + 意图关键词”双重匹配,再通过标题加权和关键词数量计算线索分数。虽然技术难度不高,但这个思路可以直接应用在独立开发者获客、自由职业接单、B2B 销售线索挖掘等场景。
如果你打算继续深入,可以按下面的路线走:
先优化关键词库:结合自己业务积累 50 个相关需求表达。
再扩展数据源:接入更多公开论坛、Reddit、V2EX、问答社区。
然后加历史库:用 SQLite 保存历史线索,形成自己的线索池。
最后考虑语义识别:用大模型过滤复杂表达,让误报进一步降低。
在使用这个工具时,请务必遵守平台规则,尊重数据来源方,也注意不要采集和保存过多个人隐私信息。它应该是帮助你更高效服务客户的助手,而不是打扰别人的工具。
如果你在实践中遇到了更刁钻的过滤场景,或者对某一层设计有更好的想法,欢迎在评论区交流。