灵猴市集Linghou Market

读取百度贴吧帖子详情

百度贴吧tieba-thread-read

用途 读取百度贴吧公开帖子详情,返回帖子标题、主楼内容和回复内容。 执行前提 请在 tieba.baidu.com 域名下的页面执行。脚本会根据参数请求 https://tieba.baidu.com/p/<id>?pn=<page>,也可以在当前页面正好是目标帖子目标页时直接读取当前 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 readBoolean(value, fallback, name) {25    if (value === undefined || value === null || value === "") return fallback;26    if (value === true || value === "true") return true;27    if (value === false || value === "false") return false;28    fail(name + " 必须是布尔值。");29  }30 31  function firstText(root, selectors) {32    for (const selector of selectors) {33      const node = root.querySelector(selector);34      const text = cleanText(node && node.textContent);35      if (text) return text;36    }37    return "";38  }39 40  function parseDataField(node) {41    const raw = node && node.getAttribute && node.getAttribute("data-field");42    if (!raw) return null;43    try {44      return JSON.parse(raw);45    } catch (_error) {46      return null;47    }48  }49 50  function detectBlocked(doc) {51    const title = cleanText(doc.title);52    const bodyText = cleanText(doc.body && (doc.body.innerText || doc.body.textContent));53    return /百度安全验证|安全验证|请完成验证|验证码|登录后|请登录|访问受限|风险|帖子不存在|主题不存在/.test(54      title + " " + bodyText,55    );56  }57 58  function hasThreadContent(doc) {59    return Boolean(doc.querySelector(".pb-content-wrap, .pb-comment-item, .core_title_txt, .l_post, .d_post_content"));60  }61 62  function idFromCurrentUrl() {63    const match = location.pathname.match(/\/p\/(\d+)/);64    return match ? match[1] : "";65  }66 67  function getThreadId() {68    const raw = input.id === undefined || input.id === null ? "" : cleanText(input.id);69    const value = raw || idFromCurrentUrl();70    if (!value) {71      fail("缺少 id 参数,且无法从当前 URL 提取帖子 ID。");72    }73    if (!/^\d+$/.test(value)) {74      fail("id 必须是数字帖子 ID。");75    }76    return value;77  }78 79  function currentPageMatches(id, page) {80    if (location.hostname !== "tieba.baidu.com") return false;81    if (idFromCurrentUrl() !== id) return false;82    const currentPage = Number(new URLSearchParams(location.search).get("pn") || "1");83    return currentPage === page;84  }85 86  async function fetchDocument(url, canUseCurrent) {87    if (canUseCurrent && !detectBlocked(document) && hasThreadContent(document)) {88      return document;89    }90 91    let fetchError = null;92    try {93      const response = await fetch(url, {94        credentials: "include",95        headers: {96          Accept: "text/html,application/xhtml+xml",97        },98      });99      if (!response.ok) {100        fail("请求贴吧帖子页失败,HTTP 状态码:" + response.status + "。");101      }102      const html = await response.text();103      const parsed = new DOMParser().parseFromString(html, "text/html");104      if (detectBlocked(parsed)) {105        fail("页面出现安全验证、登录墙或帖子不存在,无法读取公开帖子。");106      }107      return parsed;108    } catch (error) {109      fetchError = error;110    }111 112    if (canUseCurrent) {113      if (detectBlocked(document)) {114        fail("当前页面出现安全验证、登录墙或帖子不存在,无法读取公开帖子。");115      }116      return document;117    }118 119    if (fetchError && fetchError.message) {120      fail(fetchError.message);121    }122    fail("请求贴吧帖子页失败,请稍后重试。");123  }124 125  function readTitle(doc) {126    const title =127      firstText(doc, [".pb-title-wrap.pc-pb-title h1", ".pb-title-wrap.pc-pb-title", ".core_title_txt", "h1"]) ||128      cleanText((doc.title || "").replace(/_百度贴吧.*$/, "").replace(/-百度贴吧.*$/, ""));129    return title;130  }131 132  function dataAuthor(data) {133    return cleanText(134      data &&135        (data.author_name ||136          data.authorName ||137          data.user_name ||138          data.userName ||139          (data.author && (data.author.user_name || data.author.userName || data.author.name))),140    );141  }142 143  function dataContent(data) {144    return cleanText(145      data &&146        data.content &&147        (data.content.content || data.content.text || data.content.post_content || data.content.postContent),148    );149  }150 151  function dataTime(data) {152    return cleanText(153      data &&154        data.content &&155        (data.content.date || data.content.time || data.content.create_time || data.content.createTime),156    );157  }158 159  function dataFloor(data) {160    const value =161      data && data.content && (data.content.post_no || data.content.postNo || data.content.floor || data.content.floor_no);162    const number = Number(value);163    return Number.isFinite(number) && number > 0 ? number : null;164  }165 166  function extractAuthor(node, data) {167    return (168      dataAuthor(data) ||169      firstText(node, [170        ".head-name",171        ".p_author_name",172        ".d_name a",173        ".user_name",174        ".user-name",175        ".j_user_card",176        ".author",177      ])178    );179  }180 181  function extractContent(node, data) {182    if (183      node &&184      node.matches &&185      node.matches(".comment-content .text, .d_post_content, .pb-content-wrap, .post_bubble_middle_inner, .content, .text")186    ) {187      const direct = cleanText(node.textContent);188      if (direct) return direct;189    }190 191    const value =192      dataContent(data) ||193      firstText(node, [194        ".comment-content .text",195        ".d_post_content",196        ".pb-content-wrap",197        ".post_bubble_middle_inner",198        ".content",199        ".text",200      ]);201    return value;202  }203 204  function extractTime(node, data) {205    const fromData = dataTime(data);206    if (fromData) return fromData;207 208    const candidates = Array.from(209      node.querySelectorAll(".tail-info, .comment-time, .pb-comment-time, .post-time, .time, [class*='time']"),210    )211      .map((item) => cleanText(item.textContent))212      .filter(Boolean);213    const matched = candidates.find((item) => /\d{4}-\d{1,2}-\d{1,2}|\d{1,2}-\d{1,2}|\d{1,2}:\d{2}/.test(item));214    return matched || "";215  }216 217  function extractFloor(node, data, fallback) {218    const fromData = dataFloor(data);219    if (fromData) return fromData;220 221    const text = firstText(node, [".floor", ".post-floor", ".tail-info", ".louzhubiaoshi_wrap"]);222    const match = text.match(/(\d+)\s*楼/);223    return match ? Number(match[1]) : fallback;224  }225 226  function normalizeMainPost(node, data, id) {227    if (!node) return null;228    const content = extractContent(node, data);229    if (!content) return null;230    return {231      author: extractAuthor(node, data),232      content,233      time: extractTime(node, data),234      url: "https://tieba.baidu.com/p/" + id,235    };236  }237 238  function normalizeReply(node, fallbackFloor) {239    const data = parseDataField(node) || {};240    const content = extractContent(node, data);241    if (!content) return null;242    return {243      floor: extractFloor(node, data, fallbackFloor),244      author: extractAuthor(node, data),245      content,246      time: extractTime(node, data),247    };248  }249 250  function parseOldPosts(doc, id, page, includeMainPost, limit) {251    const oldPosts = Array.from(doc.querySelectorAll(".l_post"));252    if (!oldPosts.length) return null;253 254    const mainData = parseDataField(oldPosts[0]) || {};255    const mainPost = includeMainPost && page === 1 ? normalizeMainPost(oldPosts[0], mainData, id) : null;256    const replyNodes = page === 1 ? oldPosts.slice(1) : oldPosts;257    const replies = [];258 259    const fallbackBase = page === 1 ? 2 : 1;260    for (const node of replyNodes) {261      const reply = normalizeReply(node, fallbackBase + replies.length);262      if (!reply) continue;263      replies.push(reply);264      if (replies.length >= limit) break;265    }266 267    return { mainPost, replies };268  }269 270  function parseNewPosts(doc, id, page, includeMainPost, limit) {271    const mainContent = doc.querySelector(".pb-content-wrap");272    const mainNode =273      mainContent &&274      (mainContent.closest(".l_post, .pb-post-area, .pb-main, .pb-thread-content, article, section") || mainContent);275    const mainPost = includeMainPost && page === 1 ? normalizeMainPost(mainNode, parseDataField(mainNode) || {}, id) : null;276    const replyNodes = Array.from(doc.querySelectorAll(".pb-comment-item"));277    const replies = [];278 279    const fallbackBase = page === 1 ? 2 : 1;280    for (const node of replyNodes) {281      const reply = normalizeReply(node, fallbackBase + replies.length);282      if (!reply) continue;283      replies.push(reply);284      if (replies.length >= limit) break;285    }286 287    if (!replies.length) {288      const contentNodes = Array.from(doc.querySelectorAll(".pb-content-wrap")).slice(page === 1 && mainContent ? 1 : 0);289      for (const node of contentNodes) {290        const reply = normalizeReply(node.closest("article, section, div") || node, replies.length + 1);291        if (!reply) continue;292        replies.push(reply);293        if (replies.length >= limit) break;294      }295    }296 297    return { mainPost, replies };298  }299 300  if (location.hostname !== "tieba.baidu.com") {301    fail("请在 tieba.baidu.com 页面执行该脚本。");302  }303 304  const id = getThreadId();305  const page = readInt(input.page, 1, 1, 50, "page");306  const limit = readInt(input.limit, 30, 1, 100, "limit");307  const includeMainPost = readBoolean(input.includeMainPost, true, "includeMainPost");308  const targetUrl = "https://tieba.baidu.com/p/" + id + "?pn=" + String(page);309  const doc = await fetchDocument(targetUrl, currentPageMatches(id, page));310 311  if (detectBlocked(doc)) {312    fail("页面出现安全验证、登录墙或帖子不存在,无法读取公开帖子。");313  }314 315  const title = readTitle(doc);316  const parsed = parseOldPosts(doc, id, page, includeMainPost, limit) || parseNewPosts(doc, id, page, includeMainPost, limit);317  const mainPost = parsed ? parsed.mainPost : null;318  const replies = parsed ? parsed.replies.slice(0, limit) : [];319 320  if (!title && !mainPost && !replies.length) {321    fail("未读取到公开帖子内容,请确认帖子 ID、页码,或检查页面是否出现安全验证。");322  }323 324  return {325    id,326    page,327    title,328    mainPost,329    replies,330    url: "https://tieba.baidu.com/p/" + id + "?pn=" + String(page),331  };332})();