LogoMultiPost

内容获取原理

src/contents/scraper.ts 文件中,我们定义了 scraper 的逻辑,用于文章发布的时候获取网页内容。

同样,我们会监听来自 Options 页面的消息,当用户在 Article 标签页中点击 获取内容 按钮时,会触发该消息,并调用 scrapeContent 函数来获取网页内容。

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.type === 'MUTLIPOST_EXTENSION_REQUEST_SCRAPER_START') {
    const scrapeFunc = async () => {
      const articleData = await scrapeContent();
      await new Promise((resolve) => setTimeout(resolve, 1000));
      sendResponse(articleData);
    };
    // 平滑滚动到页面底部
    window.scrollTo({
      top: document.body.scrollHeight,
      behavior: 'smooth',
    });
 
    // 监听滚动完成事件
    const checkScrollEnd = () => {
      if (window.innerHeight + window.pageYOffset >= document.body.offsetHeight - 2) {
        // 滚动完成,发送响应
        scrapeFunc();
      }
    };
 
    window.addEventListener('scroll', checkScrollEnd);
 
    // 设置超时,以防滚动没有触发完成事件
    setTimeout(() => {
      window.removeEventListener('scroll', checkScrollEnd);
      scrapeFunc();
    }, 5000); // 5秒后超时
  }
  return true; // 保持消息通道开放
});

默认我们会使用 defaultScraper 函数来获取网页内容,其次它会根据网页的 URL 来判断使用哪个 scraper 函数。

例如 https://blog.csdn.net/ 会使用 scrapeCSDNContent 函数来获取网页内容。

export default async function scrapeContent(): Promise<ArticleData | undefined> {
  const url = window.location.href;
 
  // 针对不同网址开头使用不同的scraper
  const scraperMap: { [key: string]: () => Promise<ArticleData | undefined> } = {
    'https://blog.csdn.net/': scrapeCSDNContent,
    'https://zhuanlan.zhihu.com/p/': scrapeZhihuContent,
    'https://mp.weixin.qq.com/s/': scrapeWeixinContent,
    'https://juejin.cn/post/': scrapeJuejinContent,
    'https://www.jianshu.com/p/': scrapeJianshuContent,
  };
 
  const scraper = Object.keys(scraperMap).find((key) => url.startsWith(key));
  if (scraper) {
    return scraperMap[scraper]();
  }
 
  return defaultScraper();
}

CSDN 为例,我们使用 scrapeCSDNContent 函数来获取网页内容。其原理是使用 Readability 库来获取网页内容,并使用 preprocessor 函数来处理网页内容,最后根据不同类型网站的特性,使用不同的选择器来获取文章标题、作者、封面、内容、摘要等信息。

export default async function scrapeCSDNContent(): Promise<ArticleData | undefined> {
  console.debug('CSDN spider ...');
 
  const preprocess = (content: string) => preprocessor(content);
 
  // 获取文章标题
  const title = document.querySelector('h1.title-article')?.textContent || '';
  
  // 获取作者信息
  const author = document.querySelector('a.follow-nickName')?.textContent || '';
  
  // 获取封面图
  const cover = document.querySelector('meta[property="og:image"]')?.getAttribute('content') || '';
  
  // 获取文章内容
  const content = document.querySelector('div#content_views')?.innerHTML || '';
  
  // 获取文章摘要
  const digest = document.querySelector('meta[property="og:description"]')?.getAttribute('content') || '';
 
  if (!title || !content) {
    console.log('failedToGetArticleContent');
    return;
  }
 
  const articleData: ArticleData = {
    title: title.trim(),
    author: author.trim(),
    cover,
    content: preprocess(content.trim()),
    digest: digest.trim()
  };
 
  return articleData;
}