微博数据怎么采集?按时间搜索帖子、获取详情与评论
通过 Just One API 按关键词和日期小时范围搜索微博,获取帖子详情,并使用 MID 与 maxId 游标读取评论。包含分页去重和保存 JSON 的 Python 示例。
按一个话题和时间段组织采集
研究微博上的产品讨论或活动反馈,可以先限定关键词与时间范围,找到相关帖子,再读取详情和评论。Just One API 提供这三个步骤对应的接口,便于把帖子和讨论内容关联保存。
本教程以“露营”为关键词,读取最多两页搜索结果,选择三条帖子获取详情,并为每条帖子读取最多两页评论。你可以替换关键词和日期,用少量请求验证自己的话题需求。
获取 Token 并先测试一页搜索
注册 Just One API 后获取 API Token,在微博关键词搜索 V2的在线文档中免费测试。一个 Token 可用于多个平台,后续按量调用;接口权限及免费测试次数以账户显示为准。
本文三个接口都使用 GET,并通过 token 参数鉴权。响应体业务 code=0 后,才继续解析数据。Python 示例从环境变量读取 Token,不打印完整请求地址。
第一步:按日期和小时搜索微博
搜索路径为 /api/weibo/search-all/v2,下列条件需要明确传入:
q:搜索关键词,例如“露营”。startDay、endDay:开始与结束日期,格式为YYYY-MM-DD。startHour、endHour:小时,取值为 0 到 23;本例分别设为 0 和 23。page:搜索页码,从 1 开始。
日期和小时都是这个接口的必填参数。示例使用 hotSort=false 和 contains=ALL;如果只关注特定媒体类型,可按文档选择 PICTURE、VIDEO、MUSIC 或 LINK。
当前公开样例把帖子放在 data.weibo_list,其中 id 用于获取详情,data.has_next_page 用于判断是否继续搜索。修改关键词或日期后,应重新开始分页。平台搜索结果适合形成研究样本,不应被当作指定时期所有相关微博的完整清单。
第二步:获取详情并读取 MID
将搜索返回的帖子 id 传给帖子详情 V1,路径为 /api/weibo/get-weibo-detail/v1。
当前公开样例中的帖子对象位于 data.status,包含 id、mid、text 和 created_at 等字段。下一步评论请求明确使用这个对象返回的 mid。搜索结果中也可能有 bid 等标识,不要假定不同标识在所有接口中都能直接互换。
脚本会先保存详情,再取 mid。若没有找到 MID,会停止并提示检查已保存的响应,而不是猜一个值继续发送评论请求。文本中可能带有链接或格式标记,清洗时保留原文,方便后续核对。
第三步:用 maxId 游标获取评论
调用帖子评论 V1,路径为 /api/weibo/get-post-comments/v1,传入 mid 和 sort=TIME。
评论接口按游标翻页。首次请求不传 maxId;下一次把响应中的 data.max_id 作为请求参数 maxId。它与搜索的 page 页码是不同机制,不要把评论游标当作页数递增。
当前公开样例中,评论结果还带有内层 data.ok 状态,成功时为 1;评论数组位于 data.data。脚本在检查状态后保存评论,按评论 ID 去重,并在以下情况停止:空列表、游标为零或空值、游标重复,或达到每帖两页的上限。
评论中附带的部分回复不等于完整评论树;同样,少量页面也不能代表所有参与讨论的人。做主题归纳时保留帖子 ID、评论 ID 和采集范围,方便说明分析依据。
Python 示例:保存帖子与评论
在 macOS 或 Linux 终端安装依赖并设置环境变量。把 Token 换成自己的值,日期改成你要研究的时间段。
python3 -m pip install requests
export JUSTONEAPI_TOKEN='your_token'
export WEIBO_START_DAY='2026-10-01'
export WEIBO_END_DAY='2026-10-07'保存代码为 weibo_sample.py,运行 python3 weibo_sample.py。最多两次搜索,加上三条帖子的详情与每帖两次评论请求,共不超过 11 次 API 请求。结果结束较早时会减少调用。
import json
import os
from datetime import datetime, timezone
from pathlib import Path
import requests
BASE_URL = "https://api.justoneapi.com"
TOKEN = os.environ.get("JUSTONEAPI_TOKEN", "")
def call_api(path, **params):
try:
response = requests.get(
BASE_URL + path,
params={"token": TOKEN, **params},
timeout=120, allow_redirects=False,
)
except requests.RequestException:
raise RuntimeError("API network error or timeout") from None
if response.status_code != 200:
raise RuntimeError(f"API HTTP {response.status_code}")
try:
result = response.json()
except ValueError:
raise RuntimeError("API response is not JSON") from None
if not isinstance(result, dict) or result.get("code") != 0:
code = result.get("code") if isinstance(result, dict) else None
raise RuntimeError(f"API failed: code={code}; check the docs")
data = result.get("data")
if not isinstance(data, dict):
raise RuntimeError("Unexpected data shape; inspect the docs")
inner = data.get("base_resp")
if isinstance(inner, dict) and inner.get("status_code") not in (0, "0"):
raise RuntimeError("Platform response was unsuccessful")
return result
def save_json(folder, name, value):
(folder / name).write_text(
json.dumps(value, ensure_ascii=False, indent=2), encoding="utf-8"
)
def require_list(data, key):
value = data.get(key)
if not isinstance(value, list):
raise RuntimeError(f"Expected a list at {key}; inspect saved JSON")
return value
def output_folder(topic):
if not TOKEN or TOKEN == "your_token":
raise RuntimeError("Set JUSTONEAPI_TOKEN before running")
run_id = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%S%fZ")
folder = Path(topic + "-output") / run_id
folder.mkdir(parents=True, exist_ok=False)
return folder
KEYWORD = "露营"
START_DAY = os.environ.get("WEIBO_START_DAY", "")
END_DAY = os.environ.get("WEIBO_END_DAY", "")
MAX_SEARCH_PAGES = 2
MAX_POSTS = 3
MAX_COMMENT_PAGES = 2
def main():
try:
start = datetime.strptime(START_DAY, "%Y-%m-%d")
end = datetime.strptime(END_DAY, "%Y-%m-%d")
except ValueError:
raise RuntimeError("Set WEIBO_START_DAY and WEIBO_END_DAY: YYYY-MM-DD") from None
if start > end:
raise RuntimeError("Start date must not be after end date")
folder = output_folder("weibo")
ids, seen = [], set()
for page in range(1, MAX_SEARCH_PAGES + 1):
result = call_api(
"/api/weibo/search-all/v2", q=KEYWORD,
startDay=START_DAY, startHour=0, endDay=END_DAY, endHour=23,
hotSort="false", contains="ALL", page=page,
)
save_json(folder, f"search-{page}.json", result)
data = result["data"]
rows = require_list(data, "weibo_list")
if not rows:
break
previous_count = len(ids)
for row in rows:
post_id = row.get("id") if isinstance(row, dict) else None
if post_id and str(post_id) not in seen:
seen.add(str(post_id))
ids.append(str(post_id))
if len(ids) == previous_count:
break
if data.get("has_next_page") not in (True, 1, "1"):
break
selected = ids[:MAX_POSTS]
save_json(folder, "selected-posts.json", selected)
for index, post_id in enumerate(selected, start=1):
detail = call_api("/api/weibo/get-weibo-detail/v1", id=post_id)
save_json(folder, f"post-{index}-detail.json", detail)
status = detail["data"].get("status")
mid = status.get("mid") if isinstance(status, dict) else None
if not mid:
raise RuntimeError("Missing post MID; inspect the saved detail")
cursor, cursors, comments, comment_ids = "", set(), [], set()
for page in range(1, MAX_COMMENT_PAGES + 1):
params = {"mid": str(mid), "sort": "TIME"}
if cursor:
params["maxId"] = cursor
result = call_api("/api/weibo/get-post-comments/v1", **params)
save_json(folder, f"post-{index}-comments-{page}.json", result)
data = result["data"]
if data.get("ok") not in (1, "1"):
raise RuntimeError("Comment response was unsuccessful")
rows = require_list(data, "data")
if not rows:
break
for row in rows:
if not isinstance(row, dict):
raise RuntimeError("Unexpected comment item")
cid = row.get("idstr") or row.get("id")
if cid is None:
raise RuntimeError("Missing comment ID; inspect saved JSON")
if str(cid) not in comment_ids:
comment_ids.add(str(cid))
comments.append(row)
next_cursor = data.get("max_id")
if next_cursor in (None, "", 0, "0"):
break
cursor = str(next_cursor)
if cursor in cursors:
break
cursors.add(cursor)
save_json(folder, f"post-{index}-comments.json", comments)
print(f"Saved {len(selected)} post samples to {folder}")
if __name__ == "__main__":
try:
main()
except RuntimeError as error:
raise SystemExit(str(error)) from None如何查看和继续分析
结果保存在 weibo-output 的独立时间目录中。selected-posts.json 记录选中的帖子,post-*-detail.json 保存详情,post-*-comments-*.json 保存原始评论页,post-*-comments.json 汇总该帖去重后的评论。
可以把评论按帖子、时间或主题整理,筛选用户关心的功能与问题。先保留原始文本,再添加自己的清洗文本、分类标签或情绪判断;把自动分类视作待复核结果,尤其要检查反讽、引用和上下文。
常见问题
能否持续跟踪一个话题?
可以在此流程上定时查询,但要保存每次关键词、时间范围和采集时间,跨批次按帖子与评论 ID 去重。搜索和评论可用范围受实际响应影响,持续运行也不等于全量舆情覆盖。
下一页重复或没有更多结果怎么办?
搜索根据 has_next_page 与新增 ID 判断;评论根据 max_id 游标判断。保留原始响应后结束当前分页,不要无限重复同一请求。脚本中的上限用于小规模测试,可在验证结果后按需求调整。
请求失败会自动重试吗?
示例不会自动重试,避免把失败变成持续请求。先区分参数、权限、限流和瞬时故障,再参考接口失败后的处理策略添加有限重试。重跑脚本会创建新目录,不覆盖旧结果。
打开微博搜索在线文档,选一个较短时间段验证搜索结果,再连接详情与评论采集。
继续使用 Just One API
登录 Dashboard 获取 Token,查看完整 API 文档,或打开 MCP GitHub 项目了解最新配置。