距離上次教程已經過了快兩週了,沒辦法啊,學業繁忙(¬、¬) (¬_¬)
本文用到的三個工具為
- cheerio:jQuery語法,幫助你在非瀏覽器環境下解析網頁用的
- 上次沒用到,這個肯定用到啦
- segment 一個基於盤古詞庫的中文分詞工具,cnode大神寫的,手動@leizongmin大神
cheerio用法
const cheerio = require('cheerio'),
$ = cheerio.load('<h2 class="title">Hello world</h2>');
$('h2.title').text('Hello there!');
$('h2').addClass('welcome');
$.html();
//=> <h2 class="title welcome">Hello there!</h2>
複製程式碼
額外用法戳這裡
segment 用法
const Segment = require('segment');
// 建立例項
const segment = new Segment();
// 使用預設的識別模組及字典,載入字典檔案需要1秒,僅初始化時執行一次即可
segment.useDefault();
// 開始分詞
console.log(segment.doSegment('這是一個基於Node.js的中文分詞模組。'));
// [ { w: '這是', p: 0 },
// { w: '一個', p: 2097152 },
// { w: '基於', p: 262144 },
// { w: 'Node.js', p: 8 },
// { w: '的', p: 8192 },
// { w: '中文', p: 1048576 },
// { w: '分詞', p: 4096 },
// { w: '模組', p: 1048576 },
// { w: '。', p: 2048 } ]
複製程式碼
但是我們一般不需要輸出詞性,也不需要輸出多餘的標點符號,所以
const result = segment.doSegment(text, {
simple: true, //不輸出詞性
stripPunctuation: true //去除標點符號
});
// [ '這是', '一個', '基於', 'Node.js', '的', '中文', '分詞', '模組' ]
複製程式碼
更高階用法見segment
全部程式碼見github
基本用法也瞭解了,接下來進入正題吧╰(●’◡’●)╮
爬取圖片
爬取到的圖片可以看到img元素上面src和自定義的data-src屬性都帶有圖片地址,至於為什麼再下面的程式碼中我沒有獲取src的值
完全是我太菜了◔ ‸◔?,img.eq(i).src
獲取不到值,只能 prop('data-src')
了
自定義屬性相容性很差勁
Internet Explorer 11+ Chrome 8+ Firefox 6.0+ Opera 11.10+ Safari 6+
熟悉正則的同學,稍微分析下圖片的地址就可以通過正則來獲取url了,以下是我給出的示例
/(https:\/\/user-gold-cdn).+?\/ignore-error\/1/g
需要注意的是/的轉義,以及惰性匹配.+?,關於惰性匹配我這裡不打算說了(稍微提一下下(//▽//),其實就是匹配符合要求的最短串),要是說起來又可以寫一大堆了
想詳細瞭解的同學可以看看這個解釋
/**
*
* @param {any} $ cheerio
* @param {any} request 請求函式
*/
function saveImg($, request) {
const img = $('.lazyload');
const origin = request.default(); //這裡是我對request進行了一個簡單的封裝,default返回未封裝的request
for (let i = 0; i < img.length; ++i) {
//data.body.match(/(https:\/\/user-gold-cdn).+?\/ignore-error\/1/g)
let src = img.eq(i).prop('data-src');
let name = src.match(/\/.{16}\?/g) && src.match(/\/.{16}\?/g)[0].slice(1, -1); //匹配出圖片名稱
if (name) {
origin.get(src).pipe(fs.createWriteStream(`./images/${name}.png`)); //愉快的下載圖片
}
}
}
複製程式碼
資料處理
介紹下用的資料結構Map,用來儲存詞頻(詞-詞出現的次數)
類似於物件,也是鍵值對的集合,但是“鍵”的範圍不限於字串,各種型別的值(包括物件)都可以當作鍵。也就是說,Object 結構提供了“字串—值”的對應,Map 結構提供了“值—值”的對應,是一種更完善的 Hash 結構實現。如果你需要“鍵值對”的資料結構,Map 比 Object 更合適。
其實對於本文來講,鍵都為字串,用物件也完全沒有問題,使用Map完全是為了嚐鮮 (●’◡’●)ノ 關於Map複製的解釋,這一點和物件又不一樣
Map複製
Object複製,作為引數傳進建構函式並不可以複製async function getPage(request, url) {
const data = await request.get({ url });
const $ = cheerio.load(data.body);
saveImg($, request);
//獲取內容
let length = $('p').length;
for (let i = 0; i < length; ++i) {
let result = segment.doSegment(
$('p') //大部分內容都是p標籤包裹的,這裡不做過複雜的處理
.eq(i)
.text(),
{
simple: true, //不輸出詞性
stripPunctuation: true //去除標點符號
}
);
result.forEach((item, key) => {
map.set(item, map.get(item) + 1 || 1); //1 + undefined || 1 => 1
});
}
map = sortToken(map);
}
function sortToken(map) {
const words = {}; //儲存詞
let mapCopy = new Map(map); //獲取副本,Map直接賦值應該也是地址引用,參見上文
map.forEach((value, key) => {
//分詞長度大於1
if (value !== 1 && key.length > 1) { //詞頻大於1且不是單個字的留下,單字沒有什麼號分析的吧?
words[key] = value;
}
if (value === 1) { //詞頻過低,直接刷了
mapCopy.delete(key);
}
});
const keys = Object.keys(words);
//排序
keys.sort((a, b) => {
return words[b] - words[a];
});
// 每篇文章詞頻最高的20個詞,有興趣瞭解的同學可以去看看top k演算法(我們是獲取前k個,它是獲取第k個,但是它這樣需要把前k個都儲存下來,用來比較哪些是前k大)
// 我這個方法只是粗略的獲取詞頻最高的20個詞,實際上會有偏差,假設第一次排序,第十一個詞詞頻為23,而第二次排序,第十個詞詞頻為12,這樣本來之前詞頻高的反被刷了
// 但這樣的好處是節省記憶體(其實是假的),真正的可以利用最大堆和利用資料庫儲存,這樣就不用存在記憶體了
// 最後爬取完了,從資料庫取出資料,再參照top k思想演算法得出結果
keys.slice(0, 20).forEach(item => {
console.log(item, words[item]);
});
//返回分詞中詞頻為1的分詞
return mapCopy;
}
複製程式碼
我爬取了最新評論前100個文章的內容進行了分析,得出了以上結果
可以看到,程式碼
,方法
,函式
,物件
,執行
,呼叫
,元件
等等跟程式碼有關的中文詞語都出現了
不過還是一個
最受歡迎,出現次數快1000次了 (」゜ロ゜)」
有興趣的同學,可以使用英文分詞進行分析,分析下程式設計師們寫文章喜歡寫什麼程式碼
以上是我分析的一篇文章裡面的英文
還可以再分析標題,然後還可以改進排序演算法,直接把整個article-content
(class)的text進行分析,而不是像我一樣,只是分析p標籤 (๑•̀_•́๑) ,最後用視覺化工具(例如e-cahrt)把資料展示出來
喜歡的同學可以star哦github
以上,如有錯誤,歡迎大家指正