爬虫系列之丁香园论坛的所有回复用户数据(下)

目录

0.写在前面

1.原理分析

2.代码实现

2.1 爬取bbs

2.2 用户个人主页

2.3 bbs与用户主页联合使用

0.写在前面

上一节直通点

爬虫系列之爬取丁香园用户主页

高能预警】本节你可以学会什么?

第一:lxml及xpath使用

第二:模拟登陆处理

第三:多页面处理

第四:MongoDB存储

第五:使用pandas存储数据到csv

第六:数据下载

第七:列表、字典处理

第八:面相对象思想

第九:正则啊等等。。。。

异常处理

对于上一节的爬虫,只是做了个简单的数据爬取及存储,但是当遇见不同的个人主页时,代码就会报错,数据就会错落,为了更好的解决这个问题,本节即对上节代码进行优化及异常处理。

bbs数据提取

除了对代码异常处理之外,还需要使得我们的代码更加智能化,我们本次实现的功能是:随便传入一个类似的bbs链接,即可获取到当前bbs里面的所有的回复用户的用户数据,从这个需求中我们提取中两个关键点。

第一:爬取bbs里面的回复用户;

第二:爬取用户数据;

第二点上节基本上已经实现,那么重点放在第一点的讲解及异常处理等。好了,废话不多说,开始实战!

项目目录

├─data

这里面存放每个用户的头像

├─each

这里面存放每个用户的csv数据

├─raw_demo

dxy_raw_profile.py# 上一节的爬取用户主页代码

all.csv# 这个是生成的文件,代码运行后才有,是对each里面的所有csv文件的集合

bbs_genspider.py# bbs论坛代码

dxy_profile.py# 用户主页代码 最后运行只需运行这个即可,记得替换raw_id = '3927842'

项目使用

根据上述项目目录修改,除此之外,需要本地安装mongodb,并创建数据库名为test,collection为dxy,记得运行前修改里面的,最后运行即可。

1.原理分析

先来看一下我们爬取的网站长什么样子?

bbs论坛

bbs论坛图

用户个人主页

用户个人主页图

以上就是我们要爬取的目标站点。

对于第一张图,红色框图标注出来都就是我们想要爬取的目标用户,除了我标注的,bbs还有其他人的回复,由于上图的楼主回复的太多,图片截不出后面其他回复用户,大家可以根据图一中的url进行检验。

第一个问题

那么现在问题来了,是不是我直接爬取那个bbs(如图1)的url,定位到左侧边栏的每一个用户,找到对应的用户名就可以了?

答:答案是对的,确实如此解决,我们上一节知道如图2所示,用户主页为,那么只要替换不同的用户名,便可以实现多个用户的爬取,哈哈,终于有数据了~~~

现在是时候轮到bbs发话了,那么问题又来了,如何从bbs中爬取相应的用户名呢?不着急,我们看图说话!

爬取用户名图

第二个问题

看出什么端倪了?

答:只要我们利用定位到标签的值,便可以迎刃而解了~~~

第三个问题

最后代码爬取完,发现怎么数据这么少呢?

答:结果发现是用户未登陆,那么你只能爬到几个数据,可是我要做研究啊,没数据,怎么搞,接下来就是模拟登陆上场了,这里的模拟登陆没有那种高大上的,也没有, 而是采用了最为低调的添加方式。

第四个问题

最后会发现还是觉得数据少啊,发现这个bbs有多页面,这个又该如何处理呢?

答:那么我们不妨尝试一下,点击下一页,看有什么规律嘛,果不其然,被我瞄到了~~~我们看图说话

注意:下图中的链接为bbs链接!

bbs链接图

每变动一页,会发现在id后面多了个,我们会发现此处的6正是页数,我们再来看一张图:

多页面分析图

那么只需要通过xpath定位到下面的最后一个标签即可,这个规律为从打开多个bbs链接中发现的!

第五个问题

但是当单页面时候,它就不会有这个,更不会有a标签,此时代码肯定报错,那么这个怎么能难倒我呢?

答:直接做个异常处理,然后设置page为1即可解决!

2.代码实现

2.1 爬取bbs

bbs的url前缀

base_url ='http://www.dxy.cn/bbs/topic/'# 在类外面

封装成类

classbbs_genspider(object):

def__init__(self, id):

self.url = base_url + id# 与base_url拼接成真实地址

获取html页面(注意填写自己的cookie!)

defget_html(self):

headers = {

'Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',

'Accept-Encoding':'gzip, deflate',

'Accept-Language':'zh-CN,zh;q=0.9',

'Cache-Control':'max-age=0',

'Connection':'keep-alive',

'Cookie':'填写自己的cookie',

'Host':'www.dxy.cn',

'Referer':'https://auth.dxy.cn/',

'Upgrade-Insecure-Requests':'1',

'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36',

}

req = requests.get(self.url, headers=headers).text

returnreq

注意事项:填写自己的cookie,填写的是登陆成功后的cookie!

解析

importrequests

fromlxmlimportetree

defget_BbsInfo(self):

raw_html = self.get_html()

selector = etree.HTML(raw_html)

# 提取bbs标题

bbs_title = selector.xpath('//table[@class="title tbfixed"]/tbody/tr/th/h1/text()')[]

bbs_title = bbs_title.strip()# 去除字符串左右的空格

print(bbs_title)

# 头像

bbs_other_avater = selector.xpath('//td[@class="tbs"]//div[@class="avatar"]/div/span/a/img/@src')

print(bbs_other_avater)

# 用户名

bbs_other_id = selector.xpath('//td[@class="tbs"]//div[@class="auth"]//a/text()')

print(bbs_other_id)

# 处理单页面异常!

try:

page = selector.xpath('//div[@class="pages"]/div[@class="num"]/a[last()]/text()')[]

print(page)

exceptIndexErrorase:

page =1

'''

到这里,我们得到了当前bbs的所有回复用户的信息

bbs_other_avater 用户的头像地址

bbs_other_id 用户的用户名

page 页面数量

'''

returnbbs_other_id,bbs_other_avater,page

注意事项:此处返回的数据为当前bbs的所有回复用户,那么问题来了,既然是回复用户,当然有些用户回复了很多次,相当于此处获得的是最原始的所有回复用户数据,那么如何去重呢?请往下看!

获取所有页面的Url

# 获取所有页面的Url

defget_AllPageUrl(self, raw_id):

bbs = bbs_genspider(raw_id)

bbs_other_id, bbs_other_avater, page = bbs.get_BbsInfo()

page_list = []

foriinrange(1, int(page) +1):

page_url = raw_id +'?ppg='+ str(i)

page_list.append(page_url)

returnpage_list

bbs用户数据去重

# 删除重复的用户

defdel_common(self, raw_id):

page_list = self.get_AllPageUrl(raw_id)

data_bbs = {}

forurlinpage_list:

bbs = bbs_genspider(url)

bbs_id, bbs_avater, page = bbs.get_BbsInfo()

bbs_data = dict(zip(bbs_id, bbs_avater))# 两列表合并成字典

forkeyinbbs_data:

ifkeynotindata_bbs:

data_bbs[key] = bbs_data[key]

bbs_id = []

bbs_avater = []

forkeyindata_bbs:

bbs_id.append(key)

bbs_avater.append(data_bbs[key])

returnbbs_id, bbs_avater

注意事项:返回的id及avater都是列表,那么此处的去重就是解决上述问题的去重,也就是说list去重操作。这里采用的算法思想是通过函数将两个list合并成字典,key为id,value为avater,那么现在问题就变为了字典去重,先定义一个新的字典,如果当前添加的数据不在新的字典里面,就往里面加,最后就得到了一个去重后的字典,对字典进行拆分为两个列表即可,返回后的两个列表就是我们最后所需要的数据。

为什么获取了头像地址呢,原因很简单,想下载到本地以供往后头像的选择!

说在最后

上述所有方法全部封装在类里面即可。

测试

raw_id ='12345'

bbs = bbs_genspider(raw_id)

bbs_id,bbs_avater = bbs.del_common(raw_id)

print("----------------------------------")

print(bbs_id)

print(len(bbs_id))

print(bbs_avater)

print(len(bbs_avater))

测试结果图

上述测试的是单页面,然后打开,自己数一下(记得去重),便跟上述打印的结果数量3一样!到这里便测试成功了!

2.2 用户个人主页

用户个人主页功能在上一节做了详细解释,这一节主要侧重于异常的处理及如何调用bbs代码,将bbs代码与个人主页代码结合使用。

上一节保持不变代码如下:

基本框架

importrequests

fromlxmlimportetree

importpymongo

importpandasaspd

fromurllib.requestimporturlretrieve

frombbs_genspiderimportbbs_genspider

base_url ='http://i.dxy.cn/profile/'

MONGO_URI ='localhost'

MONGO_DB ='test'# 定义数据库

MONGO_COLLECTION ='dxy'# 定义数据库表

classdxy_spider(object):

# 初始化

def__init__(self, user_id, mongo_uri, mongo_db):

self.url = base_url + user_id

self.client = pymongo.MongoClient(mongo_uri)

self.db = self.client[mongo_db]

# 获取html

defget_html(self):

headers = {

'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36',

}

req = requests.get(self.url, headers=headers).text

# print(req)

returnreq

defSave_MongoDB(self,userinfo):

self.db[MONGO_COLLECTION].insert(userinfo)

self.client.close()

# 每个用户信息存储至csv文件

defSava_Excel(self, userinfo):

key_list = []

value_list = []

forkey, valueinuserinfo.items():

key_list.append(key)

value_list.append(value)

key_list.insert(,'用户名')# 增加用户名列

value_list.insert(, user)# 增加用户名

data = pd.DataFrame(data=[value_list],columns=key_list)

print(data)

'''

表示以用户名命名csv文件,并去掉DataFame序列化后的index列(这就是index=False的意思),并以utf_8_sig编码,

防止中文乱码。

注意:一定要先用pandas的DataFrame序列化后,方可使用to_csv方法导出csv文件!

'''

data.to_csv('./each/'+ user +'.csv', encoding='utf_8_sig', index=False)

注意:与上节相比,唯一变动的是,修改为,防止有些电脑打开csv文件乱码!

大改动如下:

异常处理

语法:

try:

xxxx

exceptExceptionase:

xxx

实战:

defget_UserInfo(self):

raw_html = self.get_html()

selector = etree.HTML(raw_html)

key_list = []

value_list = []

force_fan_dd_key = selector.xpath('//div[@class="follows-fans clearfix"]//p/text()')

force_fan_dd_value = selector.xpath('//div[@class="follows-fans clearfix"]//p/a/text()')

if'关注'inforce_fan_dd_key:

foreachinforce_fan_dd_key:

key_list.append(each)

foreachinforce_fan_dd_value:

value_list.append(each)

else:

key_list = ['关注','粉丝','丁当']

value_list = ['无','无','无']

UserInfo_dict = dict(zip(key_list, value_list))# 两个list合并为dict

# print(UserInfo_dict) # {'关注': '28', '粉丝': '90', '丁当': '1128'}

try:

user_home = selector.xpath('//p[@class="details-wrap__items"]/text()')[]

# user_home = user_home.replace(',','') # 去掉逗号,否则使用MongoDB可视化工具导出csv文件报错!

user_home = user_home.replace(',',',')# 改变英文逗号为中文逗号,否则使用MongoDB可视化工具导出csv文件报错!

print(user_home)

UserInfo_dict['地址'] = user_home

exceptIndexErrorase:

UserInfo_dict['地址'] ='无'

print('地址缺少,报错!')

try:

user_profile = selector.xpath('//p[@class="details-wrap__items details-wrap__last-item"]/text()')[]

user_profile = user_profile.replace(',',',')# 改变英文逗号为中文逗号,否则使用MongoDB可视化工具导出csv文件报错!

UserInfo_dict['座右铭'] = user_profile

exceptIndexErrorase:

UserInfo_dict['座右铭'] ='无'

print('座右铭缺少,报错!')

# 帖子被浏览

try:

article_browser = selector.xpath(

'//li[@class="statistics-wrap__items statistics-wrap__item-topic fl"]/p/text()')

UserInfo_dict[article_browser[]] = article_browser[1]

exceptIndexErrorase:

UserInfo_dict['帖子被浏览'] ='0次'

print('帖子被浏览缺少,报错!')

# 帖子被投票

try:

article_vote = selector.xpath(

'//li[@class="statistics-wrap__items statistics-wrap__item-vote fl"]/p/text()')

UserInfo_dict[article_vote[]] = article_vote[1]

exceptIndexErrorase:

UserInfo_dict['帖子被投票'] ='0次'

print('帖子被投票缺少,报错!')

# 帖子被收藏

try:

article_collect = selector.xpath(

'//li[@class="statistics-wrap__items statistics-wrap__item-fav fl"]/p/text()')

UserInfo_dict[article_collect[]] = article_collect[1]

exceptIndexErrorase:

UserInfo_dict['帖子被收藏'] ='0次'

print('帖子被收藏缺少,报错!')

# 在线时长共

try:

onlie_time = selector.xpath(

'//li[@class="statistics-wrap__items statistics-wrap__item-time fl"]/p/text()')

UserInfo_dict[onlie_time[]] = onlie_time[1]

exceptIndexErrorase:

UserInfo_dict['在线时长共'] ='0次'

print('在线时长共缺少,报错!')

returnUserInfo_dict

解释

这里采用异常处理原因如下:

异常结果图

上图我们发现壹刀的数据乱了,那么我们现在进入这个人的主页来看。浏览器输入,会发现重定向进入这个页面,如下图所示:

壹刀主页图

会发现这个已经不是我们想要的用户页面图了。。。所以自然那些数据爬取不到,就会报错。。这样就得加异常处理!如下图所示为加异常处理后的结果,会发现已经对齐!

正常结果图

异常处理前,数据不对齐,还有另外一种就是这个人的用户主页没了。。。对,没错就是404。。。

如:浏览器打开,会看到下图所示页面,直接404了,自然没数据,没数据,自然报错,这就是异常处理的作用!

404页面图

2.3 bbs与用户主页联合使用

下载头像

defDownLoadUserAvater(self, bbs_avater, bbs_id):

urlretrieve(bbs_avater,'./data/.jpg'.format(bbs_id))

程序调用

if__name__ =='__main__':

raw_id ='3927842'

bbs = bbs_genspider(raw_id)

bbs_id,bbs_avater = bbs.del_common(raw_id)

print("----------------------------------")

print(bbs_id)

print(len(bbs_id))

print(bbs_avater)

print(len(bbs_avater))

i =

foruserinbbs_id:

dxy = dxy_spider(user, MONGO_URI, MONGO_DB)

userinfo = dxy.get_UserInfo()

print('-----------')

print(userinfo)

dxy.Save_MongoDB(userinfo)

dxy.DownLoadUserAvater(bbs_avater[i], user)

dxy.Sava_Excel(userinfo)

# 合并each里面所有的单个用户数据,并存储至all.csv

df = pd.read_csv('./each/'+ user +'.csv', engine='python', encoding='utf_8_sig')

ifi ==:

df.to_csv('all.csv', encoding="utf_8_sig", index=False, mode='a+', header=True)

else:

df.to_csv('all.csv', encoding="utf_8_sig", index=False, mode='a+', header=False)

i +=1

效果呈现

终端log

用户名结果图

头像链接结果图

数据库

本地

MongoDB数据存储结果图

本地头像数据图

本地单个用户数据图

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20180915G1OL2200?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励