Python 爬虫专栏计算机杂谈爬虫专题

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

2017-04-11  本文已影响286人  Wakingup88688

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

(先放个封面图)


一代偶像 张国荣

</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等方法

陷阱与知识点的大杂烩

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

上一篇下一篇

猜你喜欢

热点阅读