<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>pc9527</title>
    <link>https://ruby-china.org/pc9527</link>
    <description/>
    <language>en-us</language>
    <item>
      <title>在 2019年 用 Rails 和 Python 做一款 RSS 阅读器 + 小圈子社交 (的服务端)</title>
      <description>&lt;p&gt;作为一个 20 年 Ruby 老粉，这篇文章主要分享一下&lt;del&gt;我一开始觉得 4 周就能搞定的项目，是如何拖延到 4 个月的&lt;/del&gt;一些经验和教训。如果对产品有兴趣，请看文末的链接。&lt;/p&gt;
&lt;h2 id="美好的规划"&gt;美好的规划&lt;/h2&gt;
&lt;p&gt;系统分析时，后端分 Web Server 和爬虫两块，作为 RSS 服务，对标 Feedly free plan 无可挑剔；社交部分对标 weibo，那就包括了：&lt;/p&gt;

&lt;p&gt;数据 model 如下：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;用户&lt;/li&gt;
&lt;li&gt;源&lt;/li&gt;
&lt;li&gt;集&lt;/li&gt;
&lt;li&gt;评论&lt;/li&gt;
&lt;li&gt;tag&lt;/li&gt;
&lt;li&gt;主题&lt;/li&gt;
&lt;li&gt;若干 HABTM 关联&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Web Server 这块也就是这些 model 的 CRUD 嘛，对于一个 CRUD 老男孩来说这有何难？&lt;/p&gt;

&lt;p&gt;爬虫这几年积累了好几种，分别是：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;使用 Python/feedparser 对应 RSS&lt;/li&gt;
&lt;li&gt;基于 Headless Chrome 的 Puppeteer，用 JavaScript 来对付使用 JavaScript 进行数据加载的 xx 新闻类网站&lt;/li&gt;
&lt;li&gt;对于成名新闻网站直接扒的 newspaper3K&lt;/li&gt;
&lt;li&gt;基于 Huginn 的监视一般 index/detail 类型的新闻网站更新&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;逻辑就是爬完了用统一的 http post 发到 Web Server 的接收 api 就齐活儿。&lt;/p&gt;
&lt;h2 id="问题来了："&gt;问题来了：&lt;/h2&gt;&lt;h3 id="诸多异构爬虫的糟糕数据结构"&gt;诸多异构爬虫的糟糕数据结构&lt;/h3&gt;
&lt;p&gt;Python/JavaScript/Ruby的爬虫都使用JSON进行数据汇总，基本的参数比如title/created_at/content/html/summary/author都没什么问题，一旦Web Server 端进行了 migration——比如 Atom 和 RSS 格式有用 published_at 的，newspaper3K 也有支持，表示文章的原始发布时间，更多爬来的文章没有这一项，会 fallback 到 created_at 上。published_at 这 column 本来没有，可以 migrate 一下吧？&lt;/p&gt;

&lt;p&gt;Migrate 之后，发现要去 4 个爬虫进行修改，还是语言异构的——简直求生不得——直到某天一位爬虫界高人一语点醒：不应该使用任何 Headless Chrome 类的工具，如果是无需登录的资源，必然可以通过分析页面，而获得爬取方法。&lt;/p&gt;
&lt;h3 id="干掉Headless Chrome"&gt;干掉 Headless Chrome&lt;/h3&gt;
&lt;p&gt;我尝试了一下，某些要先下载 js 执行后再渲染的网站，手工进行 requests.get 和 parse 也很方便，这一步还可以数据化，在配置文件里写一段代码进行邪恶的 eval（配置文件里的代码都是自己准备的，不会有被黑的风险，没那么邪恶...吧）&lt;/p&gt;

&lt;p&gt;关键是：省内存啊！为了无脑进行 DOM 生成和渲染需要加载 Headless Chrome 的 V8 引擎，连带这网站上所有的广告——最多我见过消耗 300M 内存的 Chrome 进程，而且如果不进行超时限制，4G 内存的豪华服务器也会被几个僵尸 worker 消耗到崩溃；设了超时吧，有些内容又加载不上——好吧，又得到人生一课：Headless Chrome 就不是拿来做生产爬虫用的。&lt;/p&gt;
&lt;h3 id="干掉Huginn"&gt;干掉 Huginn&lt;/h3&gt;
&lt;p&gt;我的 Huginn 是跑在 Docker 里，只负责监视 URL，有动静了就按照预定义的规则扒下来 post 出去——用 Python 改造一下也可以嘛！那些 Huginn 自带的 DOM 操作，Python 用 selectolax.parser 也可以做。干掉 Huginn 之后，省了不少装 Docker Image 的硬盘空间。&lt;/p&gt;
&lt;h3 id="因为要NLP所以终于同构了"&gt;因为要 NLP 所以终于同构了&lt;/h3&gt;
&lt;p&gt;每篇文章都要生成 summary 和 tags，原来的 Ruby 和 JavaScript 爬虫不能做，得靠 crontab 从 Web Server 里拿出来进行二次处理 (蛋疼到无以复加），把异构的爬虫重构以后，现在同构了，都遵循如下步骤：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;从网站配置文件找 index-detail&lt;/li&gt;
&lt;li&gt;看是否爬过了（使用本地的 sqlite3 存已经 post 成功的 url 和 title）&lt;/li&gt;
&lt;li&gt;爬取文章本身&lt;/li&gt;
&lt;li&gt;就地 NLP&lt;/li&gt;
&lt;li&gt;连 summary/tags 一起 post 到 Web Server，一次成功&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="好像也不需要那么久啊？"&gt;好像也不需要那么久啊？&lt;/h3&gt;
&lt;p&gt;只考虑 API only 的 Rails 服务和上述爬虫的确如此，但涉及到洗爬来的数据和反复重构，理论上的活儿也不少——还没到实战呢：&lt;/p&gt;

&lt;p&gt;基本功能上线后，先拿 V2EX 月经贴：“诸位大佬平时写博客吗”里收集来的博客地址进行测试。&lt;/p&gt;
&lt;h3 id="实战开始"&gt;实战开始&lt;/h3&gt;
&lt;p&gt;为了易用性，可以直接输入博客的首页 HTML 地址而不是 RSS URL 添加源，这就需要在 Rails 里用 Ruby 对 HTML/RSS 进行预处理，一百多个博客录入下来：有超时的，有没提供 RSS/Atom 的，有拒绝除了 Webkit 之外 Agent 的（那你干嘛还出 RSS?），还有输出 RSS 格式不对的......还好 Feedjira 和 Nokogiri 够给力！&lt;/p&gt;
&lt;h3 id="那也不到4个月吧？"&gt;那也不到 4 个月吧？&lt;/h3&gt;
&lt;p&gt;作为全（hen）周（cha）期（qian）开发者，锅当然推到前端的 Vue/Element UI/Buefy一家三口身上了！不过那就是另一个故事了...&lt;/p&gt;
&lt;h2 id="成品"&gt;成品&lt;/h2&gt;
&lt;p&gt;阅读器已经做好上线了，产品逻辑在&lt;a href="https://www.v2ex.com/t/591634#reply19" rel="nofollow" target="_blank" title=""&gt;这篇文章&lt;/a&gt;里说了，有兴趣可以去体验一下：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://m.readcog.cn/" rel="nofollow" target="_blank" title=""&gt;移动版&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.readcog.cn/" rel="nofollow" target="_blank" title=""&gt;桌面版&lt;/a&gt;&lt;/p&gt;</description>
      <author>pc9527</author>
      <pubDate>Wed, 14 Aug 2019 22:32:34 +0800</pubDate>
      <link>https://ruby-china.org/topics/38943</link>
      <guid>https://ruby-china.org/topics/38943</guid>
    </item>
  </channel>
</rss>
