美文网首页Python 爬虫专栏计算机杂谈爬虫专题
爬取豆瓣有关张国荣日记(二)—— 策略源码知识点

爬取豆瓣有关张国荣日记(二)—— 策略源码知识点

作者: Wakingup88688 | 来源:发表于2017-04-11 22:20 被阅读286次

    本篇介绍爬取豆瓣日记的策略分析、源码剖析、知识点汇总

    (先放个封面图)


    一代偶像 张国荣

    </br>
    本来想用Scrapy来爬的,结果连续被ban。
    设置动态UA、加Cookies、用vpn也无济于事,辗转一天多,累觉不爱。

    反爬机制不要太强啊,给豆瓣小组点个赞,跪服!!

    9086036730690
    </br>
    不过,最后还是用一般方法的解决了
    说来也奇怪,大概因为Scrapy是异步多线程,所以容易被发现吧。
    </br>

    一、目标

    爬取豆瓣所有关于张国荣的日记
    1、获取每一篇标题、作者、链接、点赞数、发布时间,将数据存入excel
    2、获取所有日记内容,存入txt
    3、将所有文章汇总,jieba分词,做成词云图
    </br>

    二、过程

    1、分析网页及源码
    豆瓣首页搜索框中输入 张国荣 回车页面跳转 以第一篇日记为例,点开 查看源码,分析得到URL 顺带分析下其它

    得到具体日记的URL,接下来就是翻页


    下拉看到显示更多 第2页 start=20 第3页start=40
    发现规律了么,每一次刷新,URL=https://www.douban.com/j/search?q=张国荣&start=n&cat=1015, 其中n=0,20,40...那我们完全可以直接这样构造,每一次获取20项内容(为一个list),不用一个一个来了,手动试了下共有n最大为2000

    </br>

    2、具体步骤及问题解决
    a、构造URL,解析json格式

    代码这样,注意range函数:

    for i in range(0,2001,20):
      response=self.get_source(url='https://www.douban.com/j/search?q=张国荣+&start='+str(i)+'&cat=1015')
      main=json.loads(response.text)['items']
      mains.append(main)
    

    </br>

    b、解析每一项获得id,由id构造每一篇链接

    直接在源码上看的话,眼花,我是for循环打印到IDE中看的
    用到BeautifulSoup和正则:

    links=[]
    #get_main()是上面解析数据的函数
    #得到包含20项内容的大list
    mains=self.get_main()
    for main in mains:
        #每一页有20项
        for j in range(0,20):
            #先找到所有h3标签
            soup=BeautifulSoup(main[j],'lxml').find('h3')
            #在其中匹配id
            pattern=re.compile(r'<a href=.*?sid:(.*?)>(.*?)</a>', re.S)
            items=re.findall(pattern, str(soup))
            for item in items:
                id=item[0][0:-3].strip()
                link='https://www.douban.com/note/'+str(id)+'/'
                #所有链接放入list中
                links.append(link)
                time.sleep(0.2)
    

    </br>
    看样子是没错,然而,运行时却出问题。
    List index out of range(抱歉忘记截图了)
    搞了好久,终于发现了,比如start=120时:

    也就是说,并不是“每一页”都有20项,有的少于20项。我以为120这里是个特殊,后面发现很多别的页面也有这样的状况。不能愉快地用range函数for循环了,好气呀。
    还好本宝宝机智,想到itertools迭代器,可是超出索引范围会报IndexError。
    想了下,加个try...except...,解决!

    #记得导入itertools模块
    import itertools
    links=[]
    mains=self.get_main()
    for main in mains:
        try:
            for j in itertools.count(0):
                xxxxxxxx(和上面那一堆一样)
        except IndexError:
            continue                 
    

    </br>

    c、提取每一页中标题、作者、日期、内容、点赞数等信息

    具体项目放入名为data的list中,多个data放入名为container的大list中

    程序跑起来没有问题,可是打开点赞数那一栏,却是空的。跑去分析源码,发现原来没那么简单。如何入手解决,想了很久。

    突然想到之前貌似看到过什么:


    发现了么,原来豆瓣分pc端和移动端,移动端是m.douban.com形式(m 即为move),试了一下,这两个页面还是有些不一样的。

    既然pc端获取不到点赞数,那么何不试试移动端。于是出现这样:


    果然有差别,看到红框框里这个data-needlogin应该觉悟,这是需要登录才能看到点赞数呀(或者喜欢),于是模拟登录。试了才知道,post方法根本就行不通。

    没辙了么,别忘了我们还有个简单的,用Cookies模拟登录也可以呀,再加个Session保持会话。

    #这里用的首页的cookies
    cookies='bid=6xgbcS6Pqds; ll="118318"; viewed="3112503"; gr_user_id=660f3409-0b65-4195-9d2f-a3c71573b40f; ct=y; ps=y; _ga=GA1.2.325764598.1467804810; _vwo_uuid_v2=112D0E7472DB37089F4E96B7F4E5913D|faf50f21ff006f877c92e097c4f2819c; ap=1; push_noty_num=0; push_doumail_num=0; _pk_ref.100001.8cb4=%5B%22%22%2C%22%22%2C1491576344%2C%22http%3A%2F%2Fwww.so.com%2Flink%3Furl%3Dhttp%253A%252F%252Fwww.douban.com%252F%26q%3D%25E8%25B1%2586%25E7%2593%25A3%26ts%3D1491459621%26t%3Df67ffeb4cd66c531150a172c69796e0%26src%3Dhaosou%22%5D; __utmt=1; _pk_id.100001.8cb4=41799262efd0b923.1467804804.35.1491576361.1491567557.; _pk_ses.100001.8cb4=*; __utma=30149280.325764598.1467804810.1491566154.1491576346.34; __utmb=30149280.3.10.1491576346; __utmc=30149280; __utmz=30149280.1491469694.24.15.utmcsr=baidu|utmccn=(organic)|utmcmd=organic; __utmv=30149280.12683; dbcl2="126831173:APSgA3NPab8"'
    headers={'User_Agent':'Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36','Cookie':cookies}
    s=requests.Session()
    response=s.get(url,headers=headers)
    requests.adapters.DEFAULT_RETRIES=5
    

    然后就是这样,加Cookies模拟登录后,用pc端的URL也可以(后面都抓的pc端)。


    到了提取具体信息的环节。还需要要注意的是,“喜欢”那一栏可能没有数据

    而且分析源码可以知道,有人点赞的有<span class="fav-num".?>(.?)</span>这一项,点赞为0的是没有这一项的。
    陷阱真多-_-|| 分情况讨论匹配正则。

    links=self.get_link()
    container=[]
    n=1
    for link in links:
        html=self.get_source(link).text
        data=[]
        #得先判断有无点赞
        patternNum=re.compile(r'class="fav-num"',re.S)
        Num=re.search(patternNum,html)
        if Num:
            pattern=re.compile(r'<h1>(.*?)</h1>.*?<a href=.*?class="note-author">(.*?)</a>.*?<span class="pub-date">(.*?)</span>.*?<div class="note" id="link-report">(.*?)</div>.*?<span class="fav-num".*?>(.*?)</span>',re.S)
            items=re.findall(pattern,html)
            for item in items:
                data.append(item[0])
                data.append(link)
                data.append(item[1])
                data.append(item[2])
                data.append(item[4])
                data.append(self.tool.replace(item[3]))
        else:
            pattern=re.compile(r'<h1>(.*?)</h1>.*?<a href=.*?class="note-author">(.*?)</a>.*?<span class="pub-date">(.*?)</span>.*?<div class="note" id="link-report">(.*?)</div>',re.S)
            items=re.findall(pattern, html)
            for item in items:
                data.append(item[0])
                data.append(link)
                data.append(item[1])
                data.append(item[2])
                #无点赞时用0代替
                data.append('0')
                data.append(self.tool.replace(item[3]))
        container.append(data)
        time.sleep(0.5)
        n+=1
    

    细心的盆友可能会叫住了,等等,这个self.tool.replace()怎么回事?

    应该想到,豆瓣日记除了文字,还有部分可能是含图片甚至音频等
    所以定义了一个Tool类,清洗多余的div、img、td等标签

    class Tool():
        def replace(self,x):
            x=re.sub(re.compile('<br>|</br>||<p>|</p>|<td>|</td>|<tr>|</tr>|</a>|<table>|</table>'), "", x)
            x=re.sub(re.compile('<div.*?>|<img.*?>|<a.*?>|<td.*?>'), "", x)
            return x.strip()
    
    class Spider():
        def __init__(self):
            self.tool=Tool()
    

    </br>

    d、保存数据入excel和txt

    目前获得标题、作者、链接、发布时间、点赞数、文章内容共6项。
    我们将前5项录入excel,最后一项存入txt。

    可以先将文章录入txt,接着list中去除文章一项, 最后录入excel。用到remove方法。
    一定警惕list=list.remove(i)这种写法!
    为什么呢,看下面:

    list=list.remove(i)其实是默认返回None,然后。。。就是个大坑啊,基础不牢地动山摇论小白学习的心酸血泪史≧﹏≦

    e、结巴分词及词云图

    这回共2000多篇文章,字数太多不可能再用语料库在线统计词频,所以采用统计权重的方法,再乘以10000放大。
    这里参考了博客:http://www.jianshu.com/p/6a285dfa3d87
    取了权重最大的前150个词(后面作词云时又相应去掉了一些)

    import jieba.analyse
    f=open(r'F:\Desktop\DouBan.txt','r')
    content=f.read()
    try:
        jieba.analyse.set_stop_words(r'F:\Desktop\TingYong.txt')
        tags=jieba.analyse.extract_tags(content,topK=150, withWeight=True)
        for item in tags:
            print item[0]+'\t'+str(int(item[1]*10000))
    finally:
        f.close()
    

    TingYong.txt是停用词表,可以在网上下载到,还可以自己修改添加。
    词云就差不多和之前一样,可见文章 http://www.jianshu.com/p/462d32450b5f
    </br>

    三、代码

    完整版代码我放github上了:https://github.com/LUCY78765580/Python-web-scraping/tree/master/DouBan
    (如果您觉得有用,star我也可以的哟~)
    豆瓣反爬比较厉害,半夜抓取可能比较好。
    </br>

    四、总结

    最后总结一下本文关键
    1、源码分析:首先分析网站及URL结构;发现豆瓣不仅有pc端还有移动端,而且两者页面不太一样;分析出不登录的话是获取不到点赞数的,同时有无点赞页面代码是不一样的。
    2、抓包获取URL,解析json格式数据
    3、Cookies模拟登录,Session保持会话
    4、数据提取与清洗:用到正则re、BeautifulSoup及自定义的Tool类
    5、用jieba.analyse编制程序,结巴分词、词频统计词云制作
    6、基础知识,range(start,end,n)、itertools.count(i)、remove等方法

    陷阱与知识点的大杂烩

    用来作为入门阶段的复习总结倒是不错的。本篇就是这样啦~

    相关文章

      网友评论

        本文标题:爬取豆瓣有关张国荣日记(二)—— 策略源码知识点

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