使用puppeteer爬取知乎文章,评论

2018-06-03  本文已影响0人  ape_caesar

初始的一些准备

    const browser = await puppeteer.launch({
        executablePath: '/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome',
        headless: true,
        args: ['--no-sandbox']
    });
    const page = await browser.newPage();
    await page.setUserAgent('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36');

先拿到browser实例;拿到page对象,并设置userAgent请求头,不然userAgent请求头中是会带有headless字样的。

要爬取知乎的内容,首先要解决登陆问题

我们通过设置cookie来解决登陆问题:
首先我们自己手动登陆知乎
进入到开发者模式后,刷新知乎,在network栏看请求信息

获取cookie

直接把整个cookie复制下来
puppeteer设置cookie的方法是

page.setCookie(options)

options 里需要有key,value,domain。这是设置cookie的基本要求
这里我写了一个方法,快速设置cookie,将刚刚复制的cookie放进去

async function addCookies(cookies_str: string, page: puppeteer.Page, domain: string){
    let cookies = cookies_str.split(';').map(pair=>{
        let name = pair.trim().slice(0,pair.trim().indexOf('='))
        let value = pair.trim().slice(pair.trim().indexOf('=')+1)
        return {name,value,domain}
    });
    await Promise.all(cookies.map(pair=>{
        return page.setCookie(pair)
    }))
}

好了,现在page.goto('https://www.zhihu.com')就可以直接进去了,不会弹登陆页面;
这里登陆之后我们可以使用await page.waitFor('div.Card');来等待页面的指定元素加载完成;

如果要模拟人的行为,我们可以await page.waitFor(2000);等待个几秒什么的,还可以模拟滚轮滑动

 for(let i = 0; i < 30; i++){
        await page.evaluate((i)=>{
            window.scrollTo(0,i*15)
        },i);
        await timeout(50);
    }

这里page.evaluate方法就是在浏览器中植入javaScript代码,这些代码都是在浏览器里执行的,比如你在evaluate方法中执行console.log('puppeteer'),你在你的执行终端是看不到输出的,你需要在浏览器的console那去看。

好的,现在不管怎么说已经进入到知乎首页
现在呢,我想把我收藏的高赞的收藏夹更新的高赞回答爬下来,怎么做呢?

首页

首先我们要找到“我的收藏”这个元素,

    const collection = await page.$('div.GlobalSideBar > div > div.Sticky > div.Card > ul.GlobalSideBar-navList > li.GlobalSideBar-starItem > a');

这里我们就通过page.$()方法,如同jquery一样通过选择器得到这个元素;
得到后我们就可以通过点击事件,进入到“我的收藏”的列表里了;
我们可以直接await collection.click();不过有时候会失灵,没有效果,我也不太清楚原因,而且这样的点击会打开一个新的page,也就是说和我们目前一直使用的这个page没有关系,比较麻烦,还需要通过如browser.pages等方法找到新打开的page,所以我们换一个思路

await page.evaluate((collection)=>{
        collection.setAttribute('target','_self');
        collection.click();
    },collection)

我们直接进入浏览器js环境去执行setAttribute方法改变新页面出现方式,再click; 要注意这里传入的参数collection必须得通过作为evaluate方法的参入辗转传进去

现在我们进入到如下页面


collection

这里呢,我们同样使用page.evaluate方法点击"不需要解释"的这个收藏夹;

await page.evaluate(()=>{
        let box = document.querySelector('#zh-favlist-following-wrap');
        if(box){
            let as = box.querySelectorAll('div.zm-item > h2 > a');
            for(let a of as){
                if(a.getAttribute('href') == '/collection/38887091'){
                    a.click();
                }
            }
        }
    })

到此,作为一个'人',puppeteer要休息一下await page.waitFor(5000);休息5秒缓一缓;
好的现在我们正式进入到高赞回答列表了

列表

现在我们开始做爬取评论的准备操作
监听page的response事件

    function response(Obj: any){
        let _Obj = Obj;
        return async function abc(response: puppeteer.Response){
            if( response.url().indexOf('/comments') > 0 ){
                console.log('装载一个知hu的评论s')
                let comments = JSON.parse(await response.text())
                let res_comments = comments.data.slice(0,10).map((data:any)=>{
                    //console.log({ name: data.author.name, content: data.content, like: data.likesCount })
                    return { name: data.author.name, content: data.content, like: data.likesCount }
                })
                _Obj.data = res_comments;
            }
        }
    }
    page.on('response', response(Obj) )

这里我们使用一个obj对象来装一个回答的前十条热评,我们通过监听response,在知乎调用'评论api'的时候,将其数据直接获取;待会我们模拟点击“展开评论”的时候,就能触发response事件并获取评论数据了

获取文字内容的代码比较多,我直接在代码里注释

for(let answer of answerlist){ //这个answerlist就是page.$$('选择器')获得的元素列表
        let data = await page.evaluate((answer)=>{
            //这个一个html转码方法  如: 
//&lt;script&gt;alert(2);&lt;/script&gt =>   <script>alert(2);</script>
            function HTMLDecode(text:string) { 
                let temp = document.createElement("div"); 
                temp.innerHTML = text; 
                let output = temp.innerText || temp.textContent; 
                return output; 
            } 
            //以下就是通过选择器,获得各种元素,然后得到我们想要的数据
           //如作者,回答内容,标题,多少个赞 等等
            let comment = answer.querySelector('div.zm-item-fav > div.zm-item-answer > div.answer-actions > div > a.toggle-comment');
            //这里我们点击评论按钮,response事件发生,我们的函数执行,收集数据
            comment.click();

            let title = answer.querySelector('.zm-item-title > a').innerHTML;

            let author = answer.querySelector('div.zm-item-fav > div.zm-item-answer > div.answer-head > div > span.name');
            let author_link = '';
            if(!author){
                author = answer.querySelector('div.zm-item-fav > div.zm-item-answer > div.answer-head > div > span > span > a.author-link').innerHTML;
                author_link = 'https://www.zhihu.com'+answer.querySelector('div.zm-item-fav > div.zm-item-answer > div.answer-head > div > span > span > a.author-link').getAttribute('href');
            }else{
                author = author.innerHTML;
            }
       
            let content = answer.querySelector('div.zm-item-fav > div.zm-item-answer > div.zm-item-rich-text > textarea.content').innerHTML;
 
            content = HTMLDecode(content);
            let like = answer.querySelector('div.zm-item-fav > div.zm-item-answer > div.zm-item-vote > a').innerHTML;

            return { title, author, content, like ,author_link}
           
        },answer)
        await timeout(2000);
        if(Obj.data.length == 1){
            await timeout(2000);
        }
        console.log(Obj.data)
        await timeout(1000)
        data.content = handler_content(data.content)
        data.comments = Obj.data;
        //这里我们就完成了一个回答的收集了
        datas.push(data);
        Obj.data = [];
        console.log(data.title)
    }
    //保存到数据库
    for(let data of datas){
        let comments = data.comments;
        delete data.comments;
        let res = await db.insert( {table: 'zhihu', data} );
        if(res  ){
            console.log(res)
            for(let c of comments){
                c.zhihu = res.insertId;
                await db.insert({table: 'zhihu_comment', data: c})
            }
        }
    }

这里其实我还有一个亮点,就是将知乎的内容那,是有不少的图片的,如果我们直接将内容显示在我们自己的网站上,这些图片是从知乎那请求不到的,会报403错误,所以我们将所有图片的src改变了一下,变成访问我的服务器,我的服务器去请求知乎的图片,再返回给我的网站,这样就ok了

最后爬的结果就在我的网站http://www.weidongwei.com/zhihu

上一篇下一篇

猜你喜欢

热点阅读