灵猴市集Linghou Market

读取百度贴吧帖子列表

百度贴吧tieba-forum-posts

用途 读取指定百度贴吧的公开帖子列表,返回帖子标题、作者、回复数、最后回复时间和帖子链接。 执行前提 请在 tieba.baidu.com 域名下的页面执行。脚本会根据参数请求 https://tieba.baidu.com/f?kw=<forum>&ie=utf-8&pn=<offset>,也可以在当前页面正好是目标贴吧目标页时直接读取当前 DOM。 如…

贴吧帖子列表
版本1.0.0
扫描passed
更新2026-06-28
超时30000ms

匹配范围

*://tieba.baidu.com/*

排除范围

未声明

能力声明

dom.read

代码

源码已展开
1return await (async () => {2  const input = typeof params === "object" && params ? params : {};3 4  function fail(message) {5    throw new Error(message);6  }7 8  function cleanText(value) {9    return String(value || "").replace(/\s+/g, " ").trim();10  }11 12  function readInt(value, fallback, min, max, name) {13    const raw = value === undefined || value === null || value === "" ? fallback : value;14    const number = Number(raw);15    if (!Number.isInteger(number)) {16      fail(name + " 必须是整数。");17    }18    if (number < min || number > max) {19      fail(name + " 必须在 " + min + " 到 " + max + " 之间。");20    }21    return number;22  }23 24  function absoluteUrl(href) {25    try {26      return new URL(href, "https://tieba.baidu.com").href;27    } catch (_error) {28      return "";29    }30  }31 32  function canonicalPostUrl(id) {33    return "https://tieba.baidu.com/p/" + id;34  }35 36  function firstText(root, selectors) {37    for (const selector of selectors) {38      const node = root.querySelector(selector);39      const text = cleanText(node && node.textContent);40      if (text) return text;41    }42    return "";43  }44 45  function parseDataField(node) {46    const raw = node && node.getAttribute && node.getAttribute("data-field");47    if (!raw) return null;48    try {49      return JSON.parse(raw);50    } catch (_error) {51      return null;52    }53  }54 55  function readNumberText(text) {56    const match = cleanText(text).replace(/,/g, "").match(/\d+/);57    return match ? Number(match[0]) : null;58  }59 60  function detectBlocked(doc) {61    const title = cleanText(doc.title);62    const bodyText = cleanText(doc.body && (doc.body.innerText || doc.body.textContent));63    return /百度安全验证|安全验证|请完成验证|验证码|登录后|请登录|访问受限|风险/.test(title + " " + bodyText);64  }65 66  function hasPostLinks(doc) {67    return Boolean(doc.querySelector('a[href*="/p/"]'));68  }69 70  function readForumFromPage() {71    const urlForum = new URLSearchParams(location.search).get("kw");72    if (urlForum) return cleanText(urlForum);73    const title = cleanText(document.title);74    const match = title.match(/^(.+?)吧(?:_|-|—|_百度贴吧| 百度贴吧|$)/) || title.match(/^(.+?)(?:_|-|—)?百度贴吧/);75    return match ? cleanText(match[1].replace(/吧$/, "")) : "";76  }77 78  function getForum() {79    const value = input.forum === undefined || input.forum === null ? "" : cleanText(input.forum);80    if (value) return value;81    const inferred = readForumFromPage();82    if (!inferred) {83      fail("缺少 forum 参数,且无法从当前 URL 或页面标题提取贴吧名称。");84    }85    return inferred;86  }87 88  function currentPageMatches(forum, page) {89    if (location.hostname !== "tieba.baidu.com") return false;90    if (location.pathname !== "/f") return false;91    const search = new URLSearchParams(location.search);92    const currentForum = cleanText(search.get("kw"));93    const currentPn = Number(search.get("pn") || "0");94    return currentForum === forum && currentPn === (page - 1) * 50;95  }96 97  async function fetchDocument(url, canUseCurrent) {98    if (canUseCurrent && !detectBlocked(document) && hasPostLinks(document)) {99      return document;100    }101 102    let fetchError = null;103    try {104      const response = await fetch(url, {105        credentials: "include",106        headers: {107          Accept: "text/html,application/xhtml+xml",108        },109      });110      if (!response.ok) {111        fail("请求贴吧论坛页失败,HTTP 状态码:" + response.status + "。");112      }113      const html = await response.text();114      const parsed = new DOMParser().parseFromString(html, "text/html");115      if (detectBlocked(parsed)) {116        fail("页面出现安全验证或登录墙,无法读取公开帖子列表。");117      }118      return parsed;119    } catch (error) {120      fetchError = error;121    }122 123    if (canUseCurrent) {124      if (detectBlocked(document)) {125        fail("当前页面出现安全验证或登录墙,无法读取公开帖子列表。");126      }127      return document;128    }129 130    if (fetchError && fetchError.message) {131      fail(fetchError.message);132    }133    fail("请求贴吧论坛页失败,请稍后重试。");134  }135 136  function extractLastReplyTime(row) {137    const text = firstText(row, [138      ".threadlist_reply_date",139      ".threadlist_author .pull-right",140      ".last_reply_time",141      ".last-time",142      ".reply-time",143      ".time",144    ]);145    if (text) return text;146    const all = cleanText(row.textContent);147    const matches = all.match(/\d{4}-\d{1,2}-\d{1,2}|\d{1,2}-\d{1,2}|\d{1,2}:\d{2}/g);148    return matches && matches.length ? matches[matches.length - 1] : "";149  }150 151  function extractReplies(row, data) {152    const dataValue =153      data && (data.reply_num || data.replyNum || data.reply_count || data.replyCount || data.comment_num);154    if (dataValue !== undefined && dataValue !== null && dataValue !== "") {155      const number = Number(dataValue);156      if (Number.isFinite(number)) return number;157    }158 159    const text = firstText(row, [160      ".threadlist_rep_num",161      ".rep_num",162      ".reply_num",163      ".reply-count",164      ".list-item-reply-num",165    ]);166    const parsed = readNumberText(text);167    return parsed === null ? 0 : parsed;168  }169 170  function extractAuthor(row, data) {171    const dataAuthor =172      data &&173      (data.author_name ||174        data.authorName ||175        data.user_name ||176        data.userName ||177        (data.author && (data.author.user_name || data.author.userName || data.author.name)));178    if (dataAuthor) return cleanText(dataAuthor);179    return firstText(row, [180      ".frs-author-name",181      ".threadlist_author a",182      ".tb_icon_author",183      ".p_author_name",184      ".user_name",185      ".user-name",186      ".j_user_card",187    ]);188  }189 190  function findRow(anchor) {191    return (192      anchor.closest("li.j_thread_list, li.threadlist_li, div.threadlist_li, li[data-field], .threadlist_text, .list-item") ||193      anchor.closest("li") ||194      anchor.parentElement ||195      anchor196    );197  }198 199  function parsePosts(doc, forum, limit) {200    const seen = new Set();201    const posts = [];202    const anchors = Array.from(doc.querySelectorAll('a[href*="/p/"]'));203 204    for (const anchor of anchors) {205      const href = anchor.getAttribute("href") || "";206      const url = absoluteUrl(href);207      const idMatch = url.match(/\/p\/(\d+)/);208      if (!idMatch) continue;209 210      const id = idMatch[1];211      if (seen.has(id)) continue;212 213      const title =214        firstText(anchor, [215          ".thread-title",216          ".title-wrap",217          ".title-content-wrap",218          ".threadlist_title",219          ".j_th_tit",220        ]) || cleanText(anchor.getAttribute("title") || anchor.textContent);221      if (!title || /^\d+$/.test(title) || /^(回复|评论|只看楼主)$/.test(title)) continue;222 223      const row = findRow(anchor);224      const data = parseDataField(row) || {};225      seen.add(id);226      posts.push({227        rank: posts.length + 1,228        id,229        title,230        forum,231        author: extractAuthor(row, data),232        replies: extractReplies(row, data),233        lastReplyTime: extractLastReplyTime(row),234        url: canonicalPostUrl(id),235      });236 237      if (posts.length >= limit) break;238    }239 240    return posts;241  }242 243  if (location.hostname !== "tieba.baidu.com") {244    fail("请在 tieba.baidu.com 页面执行该脚本。");245  }246 247  const forum = getForum();248  const page = readInt(input.page, 1, 1, 20, "page");249  const limit = readInt(input.limit, 20, 1, 50, "limit");250  const targetUrl =251    "https://tieba.baidu.com/f?kw=" + encodeURIComponent(forum) + "&ie=utf-8&pn=" + String((page - 1) * 50);252  const doc = await fetchDocument(targetUrl, currentPageMatches(forum, page));253 254  if (detectBlocked(doc)) {255    fail("页面出现安全验证或登录墙,无法读取公开帖子列表。");256  }257 258  const posts = parsePosts(doc, forum, limit);259  if (!posts.length) {260    fail("未读取到公开帖子,请确认贴吧名称、页码,或检查页面是否出现安全验证。");261  }262 263  return posts;264})();