美文网首页
使用puppeteer爬取知乎文章,评论

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

作者: ape_caesar | 来源:发表于2018-06-03 21:00 被阅读0次

    初始的一些准备

        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

    相关文章

      网友评论

          本文标题:使用puppeteer爬取知乎文章,评论

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