美文网首页
ruby如何抓取html并转换成pdf?

ruby如何抓取html并转换成pdf?

作者: 护念 | 来源:发表于2018-01-13 22:49 被阅读0次

    起因

    事情的起因是这样的,我是全栈营的会员,可以在全栈营的网站进行学习。但是网站的运维时间到今年2月份就结束了,意味着2月份之后我将不能再在上面继续学习了。

    要知道那上面的知识含金量是特别高的,我萌生将全栈营网站的内容抓取下来,制作成PDF供以后学习的想法。

    行动

    考虑到这件事很有价值,我从本周一就开始琢磨这件事,由于周内白天都在上班,所以只能在晚上空闲时间到google兜兜转转、找找方法。还好,今天周六折腾了一天终于让我找到了方法啦。嘻嘻!

    先来看看我的成果吧!截图如下:


    Snip20180113_3.png
    Snip20180114_4.png
    Snip20180114_5.png

    方法

    在说具体步骤之前,我先说下,大致的思路:

    第一步:先用爬虫的方法,将网页内容抓取下来,写入本地文件;
    第二步:利用在线网站将抓取的html文件转化成pdf;

    一、 抓取网页html、写入文件

    脚本初探
    首先这里需要先装两个gem:
    gem install rest-client 用于发送请求
    gem install nokogiri 用于解析html

    然后发送请求时,由于全栈营的网站是要求要登录验证身份的,简单的处理方法是,发送请求时带上cookie参数
    有同学反应不知道哪找cookie参数,下面简单介绍下:

    浏览器右键进入检查(inspect)


    Snip20180115_11.png
    Snip20180115_12.png
    Snip20180115_14.png

    下面我们新建一个脚本文件(任意一个.rb文件),试试看能不能成功抓取数据。

    # test.rb 
    require 'rest-client'
    require 'nokogiri'
    
    url       = 'https://fullstack.qzy.camp/posts/860'        # 随意测试一个url
    cookie    = '_quanzhan_session=你复制的cookie值放这里'       # 你在登录全栈营时,浏览器中cookie值
    response  = RestClient.get url, {Cookie: cookie}          # 必须传cookie参数(如果需要登录)
    doc       = Nokogiri::HTML.parse(response.body)           # 解析
    puts doc
    

    终端执行:ruby test.rb
    如果看到下面画面,表示成功了。

    Snip20180113_5.png

    分析网页源码,确定抓取部分
    让我们先来看看全栈营网页的源码:

    # 第一个片段,发现这里的大标题(Web API 设计实作)
    <div class="left-block hidden-xs">
       <h1><a href="/courses/38/syllabus">Web API 设计实作</a></h1>
    </div>
    
    # 第二个片段,发现这里有小标题(所属章节:7. Jbuilder 用法)
     <div class="des-text">
        <h4>所属章节:7. Jbuilder 用法</h4>
        <p><p>本章预计学习时间: 1小时半以内</p></p>
        <p><p>再学习5节就可以完成本章了</p></p>
     </div>
     
    # 第三个片段,主体内容
    <div class="post group">
        <div class="post-content markdown">
          <p>新增 <code>app/views/api/v1/trains/show.json.jbuilder</code> 档案,这就是 JBuilder 样板,用来定义 JSON 长什么样子:</p>
    ....略
    
                <p>用浏览器浏览 <code>http://localhost:3000/api/v1/trains/0822</code> 确认正常。</p>
        </div>
    </div>
    

    好了确定了要抓取的主要内容就可以进入下一步,完善脚本,并写入文件

    require 'rest-client'
    require 'nokogiri'
    
    url       = 'https://fullstack.qzy.camp/posts/860'        # 随意测试一个url
    cookie    = '_quanzhan_session=你复制的cookie值放这里'       # 你在登录全栈营时,浏览器中cookie值
    response  = RestClient.get url, {Cookie: cookie}          # 必须传cookie参数(如果需要登录)
    doc       = Nokogiri::HTML.parse(response.body)           # 解析
    
    + # 分解html
    + them      = doc.css("h1")[0].to_s            # 大标题
    + chapt     = doc.css(".des-text h4").to_s     # 小标题
    + post      = doc.css(".post").to_s            # 主体内容
    + content   = them + chapt + post              # 组合
    
    + # 文件写入 
    + file = File.new("page.erb", 'w')
    + file.write(content)
    - puts doc
    

    终端运行:ruby test.rb
    如果你的本地文件page.erb中有html正常写入,则表示正常。

    Snip20180113_7.png

    批量抓取多个页面
    现在我们能抓取单个页面了,但是我想要的效果是一下子抓取多个页面,怎么办呢?
    让我们看看全栈营网址规律:

    Snip20180113_9.png
    Snip20180113_10.png
    Snip20180113_8.png
    稍加比较我们就可以知道,只需改变请求最后的数字就可以批量抓取了。
    比如抓取web api这部分内容,代码如下:
    require 'rest-client'
    require 'nokogiri'
    
    basic_url   = 'https://fullstack.qzy.camp/posts/'           # 基础url
    cookie    = '_quanzhan_session=你复制的cookie值放这里'                                     
    
    (825..865).each do |p| 
      url = basic_url + p.to_s                                  
      response  = RestClient.get url, {Cookie: cookie}          # 必须传cookie参数(如果需要登录)
      doc       = Nokogiri::HTML.parse(response.body)
      
      # 分解html
      them      = doc.css("h1")[0].to_s            # 大标题
      chapt     = doc.css(".des-text h4").to_s     # 小标题
      post      = doc.css(".post").to_s            # 主体内容
      content   = them + chapt + post              # 组合
      
      # 文件写入 
      file = File.new("page.erb", 'w')
      file.write(content)
      puts "#{url}------已成功抓取"
    end
    

    执行后画面如下


    Snip20180113_11.png

    完善脚本
    做到这里你的确可以抓取数据了,但是还有三点需要完善:

    1、 页面现在是没有带样式的,需要美化(定义css)
    2、 前面通过输入一个连续的post编号的方式,批量获取数据的方式,并非万能的(有的地方post 号是不连续的),完善为抓取页面下页的链接
    3、 导出的pdf让目录正常

    require 'rest-client'
    require 'nokogiri'
    
    style = "<style>.frame {
        margin-left: 30px;
        margin-right: 30px;
    }
    
    h1, h2, h3, h4, h5, h6 {
        font-weight: normal;
    }
    
    .view-count {
        float: right;
        margin-top: -54px;
        color: #9B9B9B;
    }
    
    .markdown h2, .markdown h3, .markdown h4 {
        text-align: left;
        font-weight: 800;
        font-size: 16px !important;
        line-height: 100%;
        margin: 0;
        color: #555;
        margin-top: 16px;
        margin-bottom: 16px;
        border-bottom: 1px solid #eee;
        padding-bottom: 5px;
    }
    
      .markdown .figure-code figcaption {
        background-color: #e6e6e6;
    
        font: 100%/2.25 Monaco, Menlo, Consolas, 'Courier New', monospace;
        text-indent: 10.5px;
        
        -moz-border-radius: 0.25em 0.25em 0 0;
        -webkit-border-radius: 0.25em;
        border-radius: 0.25em 0.25em 0 0;
        -moz-box-shadow: inset 0 0 0 1px #d9d9d9;
        -webkit-box-shadow: inset 0 0 0 1px #d9d9d9;
        box-shadow: inset 0 0 0 1px #d9d9d9;
    }
    
    .markdown {
        position: relative;
        line-height: 1.8em;
        font-size: 14px;
        text-overflow: ellipsis;
        word-wrap: break-word;
        font-family: 'PT Serif', Georgia, Times, 'Times New Roman', serif !important;
    }
    
    .markdown ol li, .markdown ul li {
        line-height: 1.6em;
        padding: 2px 0;
        color: #333;
        font-size: 16px;
    }
    
    .markdown .figure-code {
        margin: 20px 0;
    }
    
    .post-content {
        padding-top: 5px;
        padding-bottom: 5px;
    }
    
    .markdown code {
        background-color: #ececec;
        color: #d14;
        font-size: 85%;
        text-shadow: 0 1px 0 rgba(255,255,255,0.9);
        border: 1px solid #d9d9d9;
        padding: 0.15em 0.3em;
    }
    
    div {
        display: block;
    }
    
    .markdown figure.code pre {
        background-color: #ffffcc !important;
    }
    
    .code .gi {
        color: #859900;
        line-height: 1.2em;
    }
    
    .code .err {
        color: #93A1A1;
    }
    
    .markdown a:link, .markdown a:visited {
        color: #0069D6 !important;
        text-decoration: none !important;
    }
    
    .markdown p {
        font-size: 16px;
        line-height: 1.5em;
    }
    
    .markdown blockquote {
        margin-left: 0 !important;
        margin-right: 0 !important;
        padding: 12px;
        border-left: 5px solid #50AF51;
        background-color: #F3F8F3;
        clear: both;
        display: block;
    }
    
    .markdown blockquote>*:first-child {
        margin-top: 0 !important;
    }
    
    .markdown blockquote>*:last-child {
        margin-bottom: 0 !important;
    }
    
    .markdown blockquote p {
        color: #222;
    }
    
    * {
        outline: none !important;
    }
    
    a:active, a:hover, a:link, a:visited {
        text-decoration: none;
    }
    
    pre {
        margin: 0;
    }
    
    .markdown img {
        vertical-align: top;
        max-width:100%;
        height:auto;
    }
    
    h1 a {
      color: #071A52;
    }
    
    h4 {
      color: #734488;
    }
    
    hr {
      border-color: #DEDEDE;
      border-width: 0.8px;
      margin-bottom: auto;
    }
    
    .end {
      height: 400px;
    }
    .end img {
      clear: both; 
      display: block; 
      margin:auto;
      margin-top: -70px; 
    }
    
    .end p {
      margin-left: 300px;
      margin-top: -100px;
      color: #FF9D76;
    }
    </style>"
    
    print "-----------请输入一个开始页面的post编号:"
    # 获取开始页面的编码 和文件名
    start_page = gets.chop
    print "--------------------请输入保存的文件名:"
    file_name  = gets.chop 
    
    # 结束画面
    page_end = "<div class='end'>
                  <img src='https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1515845318684&di=399b355dd05f4eeb015b087061656115&imgtype=0&src=http%3A%2F%2Fimgsrc.baidu.com%2Fforum%2Fw%253D580%2Fsign%3Dc775b978013b5bb5bed720f606d2d523%2F248ea813632762d018421c6ca2ec08fa503dc64c.jpg'>
                  <p>又学完一篇好开森!</p>
                </div>"
    
    # 写入样式
    file = File.new("#{file_name}.html", 'w')
    file.write(style)
    
    # 基础链接
    basic_url = 'https://fullstack.qzy.camp'
    url       = basic_url + '/posts/' + start_page
    cookie    = '_quanzhan_session=你复制的cookie值放这里'
    
    puts "---------------------------已开始抓取数据:请耐心等候"
    while (url != 'end')  
      # 请求数据
      response = RestClient.get url, {Cookie: cookie}
      doc      = Nokogiri::HTML.parse(response.body)
      
      # 当post存在时,解析
      if !doc.css(".post").to_s.empty?
        title              = doc.css(".post-title-h1.markdown h1").to_s
        chapt              = doc.css(".des-text h4").to_s + '<hr>'
        post               = doc.css(".post").to_s + page_end
        content            = title + chapt + post
        page               = "<div class='frame'>#{content}</div>"
        # 写入本page数据
        file.write(page)
        puts "#{url}----------中数据已成功抓取"
    
        # 计算下一个请求url
        next_relative_path = doc.css("li.next a")[0]['href'].to_s
        # 如果解析出来是 /dashboard 则代表本课结束
        url = next_relative_path == '/dashboard' ? 'end' : (basic_url + next_relative_path) 
      end
    end
    puts "---------------------------本课数据已全部抓取 😊"
    

    二、 将抓取html转化成pdf

    这里利用 在线转换工具
    把抓取的html文件上传到在线工具,转换成pdf后下载即可。

    PS: 最后如果觉得简书显示代码的方式不太友好,欢迎访问我的博客:
    http://dmy-blog.logdown.com/posts/4739981-how-does-ruby-crawl-web-content-and-make-it-into-pdf

    相关文章

      网友评论

          本文标题:ruby如何抓取html并转换成pdf?

          本文链接:https://www.haomeiwen.com/subject/mauhoxtx.html