灵猴市集Linghou Market

微信读书图书搜索

微信读书weread-book-search

用途 在微信读书公开搜索中按关键词查找图书,返回标题、作者、bookId 和阅读页链接。 执行前提 在 https://weread.qq.com/ 域名下的任意页面执行。脚本读取公开搜索接口和公开搜索结果页,不要求登录;如果站点临时限制访问,可能返回 HTTP 错误或空结果。 参数 query:必填,搜索关键词。也兼容 keyword。 limit:可选…

搜索图书公开读取
版本1.0.0
扫描passed
更新2026-06-28
超时30000ms

匹配范围

*://weread.qq.com/*

排除范围

未声明

能力声明

dom.read

代码

源码已展开
1return await (async () => {2  const input = params && typeof params === "object" ? params : {};3  const query = String(input.query || input.keyword || "").trim();4  const limit = Math.max(1, Math.min(Number(input.limit || 10) || 10, 50));5 6  if (!query) {7    throw new Error("缺少参数 query");8  }9 10  const origin = "https://weread.qq.com";11 12  const fetchJson = async (url) => {13    const response = await fetch(url.toString(), {14      credentials: "include",15      headers: {16        Accept: "application/json, text/plain, */*",17      },18    });19 20    if (!response.ok) {21      throw new Error(`请求失败:HTTP ${response.status}`);22    }23 24    return await response.json();25  };26 27  const decodeText = (value) => String(value || "").replace(/\s+/g, " ").trim();28  const buildIdentity = (title, author) => `${decodeText(title)}\u0000${decodeText(author)}`;29 30  const countBy = (items, getKey) => {31    const counts = new Map();32    for (const item of items) {33      const key = getKey(item);34      if (!key || key === "\u0000") {35        continue;36      }37      counts.set(key, (counts.get(key) || 0) + 1);38    }39    return counts;40  };41 42  const searchUrl = new URL("/web/search/global", origin);43  searchUrl.searchParams.set("keyword", query);44 45  const htmlUrl = new URL("/web/search/books", origin);46  htmlUrl.searchParams.set("keyword", query);47 48  const [data, htmlResponse] = await Promise.all([49    fetchJson(searchUrl),50    fetch(htmlUrl.toString(), {51      credentials: "include",52      headers: {53        Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",54      },55    }),56  ]);57 58  if (!htmlResponse.ok) {59    throw new Error(`搜索页面请求失败:HTTP ${htmlResponse.status}`);60  }61 62  const html = await htmlResponse.text();63  const doc = new DOMParser().parseFromString(html, "text/html");64  const htmlEntries = Array.from(doc.querySelectorAll("li.wr_bookList_item"))65    .map((item) => {66      const link = item.querySelector("a.wr_bookList_item_link");67      const title = decodeText(item.querySelector(".wr_bookList_item_title")?.textContent);68      const author = decodeText(item.querySelector(".wr_bookList_item_author")?.textContent);69      const href = link?.getAttribute("href") || "";70 71      return {72        title,73        author,74        readerUrl: href ? new URL(href, origin).toString() : "",75      };76    })77    .filter((item) => item.title && item.readerUrl);78 79  const books = Array.isArray(data?.books) ? data.books : [];80  const normalizedBooks = books.map((item) => ({81    title: decodeText(item?.bookInfo?.title),82    author: decodeText(item?.bookInfo?.author),83    bookId: decodeText(item?.bookInfo?.bookId),84  }));85 86  const exactQueues = new Map();87  const titleOnlyQueues = new Map();88 89  for (const entry of htmlEntries) {90    const key = entry.author ? buildIdentity(entry.title, entry.author) : entry.title;91    const queues = entry.author ? exactQueues : titleOnlyQueues;92    const current = queues.get(key) || [];93    current.push(entry.readerUrl);94    queues.set(key, current);95  }96 97  const apiIdentityCounts = countBy(normalizedBooks, (item) => buildIdentity(item.title, item.author));98  const htmlIdentityCounts = countBy(htmlEntries.filter((item) => item.author), (item) => buildIdentity(item.title, item.author));99  const apiTitleCounts = countBy(normalizedBooks, (item) => item.title);100  const htmlTitleCounts = countBy(htmlEntries, (item) => item.title);101 102  const takeReaderUrl = (book) => {103    const identityKey = buildIdentity(book.title, book.author);104    const titleKey = book.title;105 106    if ((apiIdentityCounts.get(identityKey) || 0) <= 1 && (htmlIdentityCounts.get(identityKey) || 0) <= 1) {107      const exact = exactQueues.get(identityKey);108      if (exact && exact.length > 0) {109        return exact.shift();110      }111    }112 113    if ((apiTitleCounts.get(titleKey) || 0) > 1 || (htmlTitleCounts.get(titleKey) || 0) > 1) {114      return "";115    }116 117    const titleOnly = titleOnlyQueues.get(titleKey);118    return titleOnly && titleOnly.length > 0 ? titleOnly.shift() : "";119  };120 121  return normalizedBooks.slice(0, limit).map((book, index) => ({122    rank: index + 1,123    title: book.title,124    author: book.author,125    bookId: book.bookId,126    readerUrl: takeReaderUrl(book),127  }));128})();