用Ruby简书爬虫统计用户文章信息

思沃大讲堂培训,要求我们把自己学习的心得感悟输出在简书上,公司还会统计大家的文章,包括文章数量、评论量、被喜欢量等等。这么多人,人工统计起来自然很麻烦,当然程序员会把这么艰巨光荣繁琐的工作交给代码,于是他们就写了一个爬虫。适值极客人正在学习Ruby,所以就突发奇想写了一个Ruby爬虫统计简书用户的文章,带动自己的Ruby学习。

如果让我抓取一个网站的内容,我的第一想法可能会是抓取它的HTML,不过也会反过来问自己一句这个网站有没有Rss订阅源地址,RSS的订阅源的内容是xml,相比html更加简洁和高效,而且由于xml的结构稳定一点(html可能那天换一个css可能就会导致我的爬虫用不了啦),解析起来会更加方便一点。在考察完简书没有提供RSS后,我就决定选择html来暴力地抓取简书了。

分析简书网址

我们可以获取用户的关注、粉丝、文章、字数、收获喜欢等信息

Paste_Image.png

我们可以获取用户文章列表,以此统计用户文章的评论量、阅读量等等,通过遍历文章列表将评论量、阅读量相加即可获取评论总量、阅读总量

需要指出的是,由于文章列表页不能把用户的文章全部列出来,而是列出10条,用户在浏览器中滚动到文章列表底部会自动加载,是用js向后台请求数据然后在前端多次拼接出来,所以想一次性地抓一次就把用户的评论总量、阅读总量是不行的,用户列表页分页的。所以我采取分页抓取的方式,那么怎么知道用户文章一共有多少页呢?我们从用户主页中获取了用户的文章总数,所以除以10加1可以获取页数

  • 用户列表页分页的,10条/页,其中第 m 页URL:

http://www.jianshu.com/users/用户ID(暂估计这么说)/latest_articles?page=m

Paste_Image.png

抓取网页,获取html

Ruby提供的HTTP访问方法十分简洁高效,当然方法不止一种,对其他方法感兴趣的同学我自行Google,在此我贴出自己的代码:

h = Net::HTTP.new("www.jianshu.com", 80)
resp = h.get "/users/#{authorInfo.id}/latest_articles"
latest_articles_html = resp.body.to_s

顾问生义,我想不需要解释代码的意思了吧
根据上面介绍的简书网址规则,就可以通过上述代码抓取到相应网页的HTML

分析抓取内容的结构

获取完相应网页的HTML内容后要做的就是分析HTML的内容和结构。我们用眼睛很容易看出网页上的内容,但是爬虫看到的只有html源代码。下面我从抓取的HTML中提取了下列有用的代码:

  • 用于提取用户的关注量、粉丝量、文章数、字数、收获喜欢数
<div class="user-stats">
  <ul class="clearfix">
    <li>
      <a href="/users/ef49e6b7ec1e/subscriptions"><b>38</b><span>关注</span></a>
    </li>
    <li>
      <a href="/users/ef49e6b7ec1e/followers"><b>22</b><span>粉丝</span></a>
    </li>
    <br>
    <li>
      <a href="/users/ef49e6b7ec1e"><b>9</b><span>文章</span></a>
    </li>
    <li>
      <a><b>9938</b><span>字数</span></a>
    </li>
    <li>
      <a><b>41</b><span>收获喜欢</span></a>
    </li>
  </ul>
</div>
  • 用于提取用户文章评论总量、阅读总量
<ul class="article-list latest-notes"><li>
    <div>
      <p class="list-top">
        <a class="author-name blue-link" target="_blank" href="/users/ef49e6b7ec1e">极客人</a>
        <em>·</em>
        <span class="time" data-shared-at="2016-12-12T15:39:07+08:00">4天之前</span>
      </p>
      <h4 class="title"><a target="_blank" href="/p/f11d1fca16c6">Hello,Ruby!</a></h4>
      <div class="list-footer">
        <a target="_blank" href="/p/f11d1fca16c6">
          阅读 23
</a>        <a target="_blank" href="/p/f11d1fca16c6#comments">
           · 评论 4
</a>        <span> · 喜欢 1</span>
        
      </div>
    </div>
  </li>
  <li class="have-img">
      <a class="wrap-img" href="/p/3d43727e04a5"><img src="http://upload-images.jianshu.io/upload_images/2154287-86190de5fd3071f7.png?imageMogr2/auto-orient/strip%7CimageView2/1/w/300/h/300" alt="300"></a>
    <div>
      <p class="list-top">
        <a class="author-name blue-link" target="_blank" href="/users/ef49e6b7ec1e">极客人</a>
        <em>·</em>
        <span class="time" data-shared-at="2016-12-08T00:02:08+08:00">9天之前</span>
      </p>
      <h4 class="title"><a target="_blank" href="/p/3d43727e04a5">Html5语义化标签的启示</a></h4>
      <div class="list-footer">
        <a target="_blank" href="/p/3d43727e04a5">
          阅读 182
</a>        <a target="_blank" href="/p/3d43727e04a5#comments">
           · 评论 1
</a>        <span> · 喜欢 10</span>
        
      </div>
    </div>
  </li>
.....
  
  <li>
    <div>
      <p class="list-top">
        <a class="author-name blue-link" target="_blank" href="/users/ef49e6b7ec1e">极客人</a>
        <em>·</em>
        <span class="time" data-shared-at="2016-11-28T19:35:45+08:00">18天之前</span>
      </p>
      <h4 class="title"><a target="_blank" href="/p/114c27b6456c">网站自动跳转到Cjb.Net的惊险之旅</a></h4>
      <div class="list-footer">
        <a target="_blank" href="/p/114c27b6456c">
          阅读 21
</a>        <a target="_blank" href="/p/114c27b6456c#comments">
           · 评论 3
</a>        <span> · 喜欢 3</span>
        
      </div>
    </div>
  </li>

</ul>

正则匹配,抠出关键信息

上面我已经提取出有用的关键的HTML,现在要做的是让爬虫做同样的事情。所以我用到啦正则匹配。

  • 正则匹配出粉丝", "关注", "文章", "字数", "收获喜欢"
#从html中加载基本用户信息
def loadAuthorBaseInfoFromHtml(authorInfo, latest_articles_html)  
infoKeys=["粉丝", "关注", "文章", "字数", "收获喜欢"]  
infoValues = Array.new(infoKeys.length) 
 if /<ul class=\"clearfix\">([\s\S]*?)<\/ul>/ =~ latest_articles_html  then            authorInfoHtml= $1.force_encoding("UTF-8")   
 for i in 0 .. infoKeys.length-1     
 if /#{"<b>([0-9]*)</b><span>#{infoKeys[i]}</span>".force_encoding("UTF-8")}/=~ authorInfoHtml        
infoValues[i]= $1      
     end    
   end 
 end  
authorInfo.setBaseInfo(infoValues[0], infoValues[1], infoValues[2], infoValues[3], infoValues[4])
end

其他匹配代码请参看源代码

整合信息,多样化地输出成果物

当统计出用户的文章信息后,就是把统计信息输出来。为了让输出的产物更加丰富和自定义程度更高,所以我采取了渲染模板的方式,将数据和界面分离。
模板文件:

<body>
<section>
    <header>
        <h1>@{title}</h1>
        <section>统计时间:@{time}</section>
    </header>
    <section id="content">
    <table>
        <thead>
        <tr>
            <th>序号</th>
            <th>姓名</th>
            <th>文章数</th>
            <th>字数</th>
            <th>阅读量</th>
            <th>收到评论</th>
            <th>收到喜欢</th>
            <th>小buddy姓名</th>
        </tr>
        </thead>
        <tbody>
        @{content}
        </tbody>
    </table>
    </section>
    <footer>@{footer}</footer>
</section>
</body>

然后在Ruby代码中加载模板文件,并将@{title}、@{time}、 @{content}、 @{content}替换真实的统计信息

  def out2Html(title)
    tplFile = open @tpl
    tplContent = tplFile.read
    tplFile.close
    content =""
    for i in 0 .. @authorList.length-1
      author = @authorList[i]
      content+=format(" <tr>
            <td>%s</td>
            <td><a target= \"_blank\" href=\"http://jianshu.com/users/%s\">%s</a></td>
            <td>%s</td>
            <td>%s</td>
            <td>%s</td>
            <td>%s</td>
            <td>%s</td>
            <td>%s</td>
        </tr>", i, author.id, author.name, author.post_count, author.word_count, author.read_count, author.comment_count, author.liked_count, author.buddy)
    end

    today = Time.new;
    timeStr= today.strftime("(%Y-%m-%d %H:%M:%S)");
    footer="Powered By <a target=\"_blank\" href=\"http://wangbaiyuan.cn\">BrainWang@ThoughtWorks</a>"
    out = tplContent.gsub(/@\{title\}/, title)
    out = out.gsub(/@\{content\}/, content)
    out = out.gsub(/@\{footer\}/, footer)
    out = out.gsub(/@\{time\}/, timeStr)
    timeStr= today.strftime("(%Y-%m-%d)");
    file=open("output/#{title+timeStr}.html","w")
    file.write out
    print "\n输出文件位于", Pathname.new(File.dirname(__FILE__)).realpath,"/",file.path
    file.close

  end

当然,那天只要加一个out2json就可轻松做一个API,实现更高的定制化效果啦

项目主页

https://github.com/geekeren/jianshu_spider

使用方法

  • 下载项目代码并运行
cd jianshu_spider/
 ruby main.rb

更详细的项目介绍请移步Github项目主页

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,482评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,377评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,762评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,273评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,289评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,046评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,351评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,988评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,476评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,948评论 2 324
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,064评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,712评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,261评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,264评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,486评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,511评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,802评论 2 345

推荐阅读更多精彩内容