Node.js的學習--使用cheerio抓取網頁數據


打算要寫一個公開課網站,缺少數據,就決定去網易公開課去抓取一些數據。

前一陣子看過一段時間的Node.js,而且Node.js也比較適合做這個事情,就打算用Node.js去抓取數據。

關鍵是抓取到網頁之后如何獲取到想要的數據呢?然后就發現了cheerio,用來解析html非常方便,就像在瀏覽器中使用jquery一樣。

使用如下命令安裝cheerio

     npm install cheerio

Cheerio安裝完成, 我們就可以開始工作了。 首先讓我們來看一段javascript代碼 這段代碼可以下載任意一個網頁的內容。將其放入到curl.js中,並導出。

var http = require("http");
 
// Utility function that downloads a URL and invokes
// callback with the data.
function download(url, callback) {
  http.get(url, function(res) {
    var data = "";
    res.on('data', function (chunk) {
      data += chunk;
    });
    res.on("end", function() {
      callback(data);
    });
  }).on("error", function() {
    callback(null);
  });
}

exports.download = download;

然后是使用cheerio解析html,找到想要的數據。

我們先來自己分析一下頁面。我們要抓取http://v.163.com/special/opencourse/englishs1.html這個頁面中的視頻,視頻的地址都在下載的按鈕里。其中一個下載按鈕的html的代碼如下:

<a class="downbtn" href="http://mov.bn.netease.com/mobilev/2013/1/F/G/S8KTEF7FG.mp4" id="M8KTEKR84" target="_blank"></a>

我們取到其中的href屬性,只需要進行如下選擇即可

$("a.downbtn").attr("href");

在是現實,我們可以在index.js中寫入如下代碼

var cheerio = require("cheerio");
var server = require("./curl");
 
var url = "http://v.163.com/special/opencourse/englishs1.html"

server.download(url, function(data) {
  if (data) {
    //console.log(data);
 
    var $ = cheerio.load(data);
    $("a.downbtn").each(function(i, e) {
        console.log($(e).attr("href"));
    });
 
    console.log("done");
  } else {
      console.log("error");
  } 
});

然后執行

     node index.js

這樣就可以在命令框里面打印出頁面上所有的視頻地址。如下圖

 

cheerio中文API [參考]

我們將用到的標記示例

<ul id="fruits">
  <li class="apple">Apple</li>
  <li class="orange">Orange</li>
  <li class="pear">Pear</li>
</ul>

這是我們將會在所有的API例子中用到的HTML標記

Loading

首先你需要加載HTML。這一步對jQuery來說是必須的,since jQuery operates on the one, baked-in DOM。通過Cheerio,我們需要把HTML document 傳進去。

這是首選:

var cheerio =require('cheerio'),
    $ = cheerio.load('<ul id="fruits">...</ul>');

或者通過傳遞字符串作為內容來加載HTML:

$ =require('cheerio');
$('ul','<ul id="fruits">...</ul>');

Or as the root:

$ =require('cheerio');
$('li','ul','<ul id="fruits">...</ul>');

你也可以傳遞一個額外的對象給.load()如果你需要更改任何的默認解析選項的話:

$ = cheerio.load('<ul id="fruits">...</ul>',{
    ignoreWhitespace:true,
    xmlMode:true});

這些解析選項都是直接來自htmlparser ,因此任何在htmlparser里有效的選項在Chreeio里也是行得通的。默認的選項如下:

{
    ignoreWhitespace:false,
    xmlMode:false,
    lowerCaseTags:false
}

Selectors

Cheerio的選擇器用起來幾乎和jQuery一樣,所以API也很相似。

$(selectior,[context],[root])

選擇器在 Context 范圍內搜索,Context又在Root范圍內搜索。selector 和context可是是一個字符串表達式,DOM元素,和DOM元素的數組,或者chreeio對象。root 是通常是HTML 文檔字符串。

$('.apple','#fruits').text()
//=> Apple

$('ul .pear').attr('class')
//=> pear

$('li[class=orange]').html()
//=> <li class="orange">Orange</li>

Attributes

獲得和修改屬性

.attr(name,value)

獲得和修改屬性。在匹配的元素中只能獲得第一元素的屬性。如果設置一個屬性的值為null,則移除這個屬性。你也可以傳遞一對鍵值,或者一個函數。

$('ul').attr('id')
//=> fruits

$('.apple').attr('id','favorite').html()
//=> <li class="apple" id="favorite">Apple</li>

.val([value])

獲得和修改input,select,textarea的value.注意: 對於傳遞鍵值和函數的支持還沒有被加進去。

$('input[type="text"]').val()
//=> input_text

$('input[type="text"]').val('test').html()
//=><input type="text" value="test"/>

.removeAttr(name)

通過name刪除屬性

$('.pear').removeAttr('class').html()
//=> <li>Pear</li>

.hasClass( className )

檢查匹配的元素是否有給出的類名

$('.pear').hasClass('pear')
//=> true

$('apple').hasClass('fruit')
//=> false

$('li').hasClass('pear')
//=> true

.addClass(className)

增加class(es)給所有匹配的elements.也可以傳函數。

$('.pear').addClass('fruit').html()
//=> <li class="pear fruit">Pear</li>

$('.apple').addClass('fruit red').html()
//=> <li class="apple fruit red">Apple</li>

.removeClass([className])

從選擇的elements里去除一個或多個有空格分開的class。如果className 沒有定義,所有的classes將會被去除,也可以傳函數。

$('.pear').removeClass('pear').html()
//=> <li class="">Pear</li>

$('.apple').addClass('red').removeClass().html()
//=> <li class="">Apple</li>

.toggleClass( className, [switch] )

添加或刪除class,依賴於當前是否有該class.

$('.apple.green').toggleClass('fruit green red').html()
//=> <li class="apple fruit red">Apple</li>

$('.apple.green').toggleClass('fruit green red', true).html()
//=> <li class="apple green fruit red">Apple</li>

.is( selector )

.is( element )

.is( selection )

.is( function(index) )

有任何元素匹配selector就返回true。如果使用判定函數,判定函數在選中的元素中執行,所以this指向當前的元素。

Traversing

.find(selector)

獲得一個在匹配的元素中由選擇器濾過的后代。

$('#fruits').find('li').length
//=> 3

.parent([selector])

獲得每個匹配元素的parent,可選擇性的通過selector篩選。

$('.pear').parent().attr('id')
//=> fruits

.parents([selector])

獲得通過選擇器篩選匹配的元素的parent集合。

$('.orange').parents().length
// => 2
$('.orange').parents('#fruits').length
// => 1

.closest([selector])

對於每個集合內的元素,通過測試這個元素和DOM層級關系上的祖先元素,獲得第一個匹配的元素

$('.orange').closest()// => []
$('.orange').closest('.apple')// => []
$('.orange').closest('li')// => [<li class="orange">Orange</li>]
$('.orange').closest('#fruits')// => [<ul id="fruits"> ... </ul>]

.next()

獲得第一個本元素之后的同級元素

$('.apple').next().hasClass('orange')
//=> true

.nextAll()

獲得本元素之后的所有同級元素

$('.apple').nextAll()
//=> [<li class="orange">Orange</li>, <li class="pear">Pear</li>]

.prev()

獲得本元素之前的第一個同級元素

$('.orange').prev().hasClass('apple')
//=> true

.preAll()

$('.pear').prevAll()
//=> [<li class="orange">Orange</li>, <li class="apple">Apple</li>]

獲得本元素前的所有同級元素

.slice(start,[end])

獲得選定范圍內的元素

$('li').slice(1).eq(0).text()
//=> 'Orange'

$('li').slice(1,2).length
//=> 1

.siblings(selector)

獲得被選擇的同級元素,不包括本身

$('.pear').siblings().length
//=> 2

$('.pear').siblings('.orange').length
//=> 1

.children(selector)

獲被選擇元素的子元素

$('#fruits').children().length
//=> 3

$('#fruits').children('.pear').text()
//=> Pear

.each(function(index,element))

迭代一個cheerio對象,為每個匹配元素執行一個函數。When the callback is fired, the function is fired in the context of the DOM element, so this refers to the current element, which is equivalent to the function parameter element.要提早跳出循環,返回false.

var fruits =[];

$('li').each(function(i, elem){
  fruits[i]= $(this).text();});

fruits.join(', ');
//=> Apple, Orange, Pear

.map(function(index,element))

迭代一個cheerio對象,為每個匹配元素執行一個函數。Map會返回一個迭代結果的數組。the function is fired in the context of the DOM element, so this refers to the current element, which is equivalent to the function parameter element

$('li').map(function(i, el){
// this === el
return $(this).attr('class');
}).join(', ');
//=> apple, orange, pear

.filter(selector)

.filter(function(index))

迭代一個cheerio對象,濾出匹配選擇器或者是傳進去的函數的元素。如果使用函數方法,這個函數在被選擇的元素中執行,所以this指向的手勢當前元素。

Selector:

$('li').filter('.orange').attr('class');
//=> orange

Function:

$('li').filter(function(i, el){
// this === el
return $(this).attr('class')==='orange';
}).attr('class')
//=> orange

.first()

會選擇chreeio對象的第一個元素

$('#fruits').children().first().text()
//=> Apple

.last()

$('#fruits').children().last().text()
//=> Pear

會選擇chreeio對象的最后一個元素

.eq(i)

通過索引篩選匹配的元素。使用.eq(-i)就從最后一個元素向前數。

$('li').eq(0).text()
//=> Apple

$('li').eq(-1).text()
//=> Pear

Manipulation

改變DOM結構的方法

.append(content,[content...])

在每個元素最后插入一個子元素

$('ul').append('<li class="plum">Plum</li>')
$.html()
//=>  <ul id="fruits">
//      <li class="apple">Apple</li>
//      <li class="orange">Orange</li>
//      <li class="pear">Pear</li>
//      <li class="plum">Plum</li>
//    </ul>

.prepend(content,[content,...])

在每個元素最前插入一個子元素

$('ul').prepend('<li class="plum">Plum</li>')
$.html()
//=>  <ul id="fruits">
//      <li class="plum">Plum</li>
//      <li class="apple">Apple</li>
//      <li class="orange">Orange</li>
//      <li class="pear">Pear</li>
//    </ul>

.after(content,[content,...])

在每個匹配元素之后插入一個元素

$('.apple').after('<li class="plum">Plum</li>')
$.html()
//=>  <ul id="fruits">
//      <li class="apple">Apple</li>
//      <li class="plum">Plum</li>
//      <li class="orange">Orange</li>
//      <li class="pear">Pear</li>
//    </ul>

.before(content,[content,...])

在每個匹配的元素之前插入一個元素

$('.apple').before('<li class="plum">Plum</li>')
$.html()
//=>  <ul id="fruits">
//      <li class="plum">Plum</li>
//      <li class="apple">Apple</li>
//      <li class="orange">Orange</li>
//      <li class="pear">Pear</li>
//    </ul>

.remove( [selector] )

從DOM中去除匹配的元素和它們的子元素。選擇器用來篩選要刪除的元素。

$('.pear').remove()
$.html()
//=>  <ul id="fruits">
//      <li class="apple">Apple</li>
//      <li class="orange">Orange</li>
//    </ul>

.replaceWith( content )

替換匹配的的元素

var plum = $('<li class="plum">Plum</li>')
$('.pear').replaceWith(plum)
$.html()
//=> <ul id="fruits">
//     <li class="apple">Apple</li>
//     <li class="orange">Orange</li>
//     <li class="plum">Plum</li>
//   </ul>

.empty()

清空一個元素,移除所有的子元素

$('ul').empty()
$.html()
//=>  <ul id="fruits"></ul>

.html( [htmlString] )

獲得元素的HTML字符串。如果htmlString有內容的話,將會替代原來的HTML

$('.orange').html()
//=> Orange

$('#fruits').html('<li class="mango">Mango</li>').html()
//=> <li class="mango">Mango</li>

.text( [textString] )

獲得元素的text內容,包括子元素。如果textString被指定的話,每個元素的text內容都會被替換。

$('.orange').text()
//=> Orange

$('ul').text()
//=>  Apple
//    Orange
//    Pear

Rendering

如果你想呈送document,你能使用html多效用函數。

$.html()
//=>  <ul id="fruits">
//      <li class="apple">Apple</li>
//      <li class="orange">Orange</li>
//      <li class="pear">Pear</li>
//    </ul>

如果你想呈送outerHTML,你可以使用 $.html(selector)

$.html('.pear')
//=> <li class="pear">Pear</li>

By default, html will leave some tags open. Sometimes you may instead want to render a valid XML document. For example, you might parse the following XML snippet:

$ = cheerio.load('<media:thumbnail url="http://www.foo.com/keyframe.jpg" width="75" height="50" time="12:05:01.123"/>');

... and later want to render to XML. To do this, you can use the 'xml' utility function:

$.xml()//=>  <media:thumbnail url="http://www.foo.com/keyframe.jpg" width="75" height="50" time="12:05:01.123"/>

Miscellaneous

不屬於其它地方的DOM 元素方法

.toArray()

取得所有的在DOM元素,轉化為數組、

$('li').toArray()
//=> [ {...}, {...}, {...} ]

.clone()

克隆cheerio對象

var moreFruit = $('#fruits').clone()

Utilities

$.root

Sometimes you need to work with the top-level root element. To query it, you can use $.root().

$.root().append('<ul id="vegetables"></ul>').html();
//=> <ul id="fruits">...</ul><ul id="vegetables"></ul>

$.contains( container, contained )

查看cotained元素是否是container元素的子元素

$.parseHTML( data [, context ] [, keepScripts ] )

將字符串解析為DOM節點數組。context參數對chreeio沒有意義,但是用來維護APi的兼容性。

 

今天就到這里了~~有機會把前面的小程序完善成一個網頁爬蟲~~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM