使用Puppeteer爬取微信文章

一朋友在羣裏問有沒有什麼辦法可以一次性把這個連接裏的文章保存下來。點開能夠看到,其實就是一個文章合集。因此需求就是,把這個文檔中的連接裏的文章挨個保存下來。保存形式能夠有不少種,能夠是圖片,也能夠是網頁。這裏由於使用puppeteer庫的緣由,故選擇保存格式格式爲PDF。html


需求解構

完成整個動做,主要分爲這兩個部分。獲取文檔內全部文章的連接;把每一個連接裏的內容保存爲PDF文件。node

對於獲取連接,有兩條路,一是使用request模塊請求該網址獲取文檔;二是把網頁保存到本地使用fs模塊獲取文檔內容。拿到文檔也就是整個HTML文檔後,一開始沒想到什麼好法子來拿到所有文章連接。若是直接在網頁那就好辦,直接DOM的quertSelectorAllAPI配合CSS選擇器就能夠很是方便地拿到全部a連接中的href屬性。但這裏是Node,是DOM外之地。又想到的是直接使用正則匹配,後來仍是放棄了這個作法。在google搜了下才發現居然忘了cheerio這個好東西。cheerio是一個專門爲服務端設計的快速靈活而簡潔得jQuery實現。git

對於保存網頁內容,我所知道的常規操做是保存爲PDF文件,恰巧以前剛知道的puppeteer知足這樣的需求。puppeteer是一個由chrome devtools團隊維護的提供了控制chrome瀏覽器高級API的一個Node庫。除去爬取網頁內容保存爲PDF文件外,它還能夠做爲服務端渲染的一個方案以及實現自動化測試的一個方案。github


需求實現

獲取連接

先上這部分代碼chrome

const getHref = function () {
    let file = fs.readFileSync('./index.html').toString()
    const $ = cheerio.load(file)
    let hrefs = $('#sam').find('a')
    for (e in hrefs) {
        if (hrefs[e].attribs && hrefs[e].attribs['href']) {
            hrefArr.push({
                index: e,
                href: hrefs[e].attribs['href']
            })
        }
    }
    fs.writeFileSync('hrefJson.json', JSON.stringify(hrefArr))
}
複製代碼

由於後面的代碼都依賴到讀取的文件,因此這裏用的是readFileSync方法。若是沒有聲明返回內容的格式,那默認是Buffer格式。能夠選擇填寫utf8格式,或者直接在該方法後面使用toString方法。npm

兩行代碼用cheerio拿到全部全部連接的DOM元素後,挨個將其處理爲方便後面要用到的格式。考慮到可能存在a標籤沒有href屬性的狀況,這裏還對其進行了判斷,不過這也是後面調試程序時才發現的bug。json

若是須要將全部的連接另外保存起來,使用writeFile方法。windows

存爲PDF

一樣,先上這部分代碼。瀏覽器

const saveToPdf = function () {
    async () => {
        const browser = await puppeteer.launch({
            executablePath: './chrome-win/chrome.exe',
        });

        // 連接計數
        let i = 0

        async function getPage() {
            const page = await browser.newPage();
            await page.goto(hrefArr[i]['href'], { waitUntil: 'domcontentloaded' });

            // 網頁標題
            let pageTitle

            if (hrefArr[i]['href'].includes('weixin')) {
                pageTitle = await page.$eval('meta[property="og:title"]', el => el.content)
            } else {
                pageTitle = await page.$eval('title', el => el.innerHTML)
            }

            let title = pageTitle.trim()
            // 去掉斜杆
            let titlea = title.replace(/\s*/g, "")
            // 去掉豎線
            let titleb = titlea.replace(/\|/g, "");
            
            await page.pdf({ path: `${i}${titleb}.pdf` });

            i++

            if (i < hrefArr.length) {
                getPage()
            } else {
                await browser.close();
            }
        }
        getPage()
    }
}
複製代碼

由於須要等待chrome瀏覽器的打開,以及其餘可能的異步請求。最外層使用了async 配合箭頭函數將真正的執行代碼包住。bash

在用 npm 安裝 puppetter時,由於默認會下載chrome瀏覽器,而服務器在國外,通常都沒法下載成功。固然也有相應的解決方案,這裏我就不展開了。若是安裝puppeteer,能夠參開這篇文章或者直接谷歌搜下。

在前一部分說到,咱們須要把不止一個連接裏的內容保存爲PDF,因此使用了變量i來標識每一次須要訪問的連接。

對於獲取網頁標題,當時確實費了點時間才處理好拿到已有連接的網頁標題。因此連接中主要有兩種網站的連接,一類是微信公衆號文章,另外一類是新浪財新這種網站。微信文章裏頭沒有像新浪這樣直接給出title內容。

1f01l8.png

1fD9xK.png

這個時候就要用到 page 類中的$eval方法,$eval方法主要有兩個參數,一是選擇器,二是在瀏覽器上下文中執行的函數。$eval方法會頁面中運行document.querySelector方法,並將其返回值傳遞給第二個參數,也就是咱們寫好的方法中。以獲取新浪網頁文章title爲例,title爲傳入選擇器,咱們須要的是其標籤內容。

pageTitle = await page.$eval('title', el => el.innerHTML)
複製代碼

在產生文件名的過程當中,因爲文件夾仍是文件路徑的一部分。此時還須要考慮到windows文件路徑規範。但網頁中的標題並不受此規範限制,由此產生矛盾。這個問題也是後面調試的時候才發現,一開始寫代碼並無想到這個問題。即須要去除標題中的斜槓豎杆還有空格等字符。

每獲取完一個連接的內容後,就將連接位置標識i + 1,知道全部連接內容保存完畢,關閉打開的網頁。

相關文章
相關標籤/搜索