在成功获取微博用户的列表之后,我们可以对每个用户的主页内容进行爬取了
环境
tools
1、chrome及其developer tools
2、python3.6
3、pycharm
Python3.6中使用的库
1 import urllib.error 2 import urllib.request 3 import urllib.parse 4 import urllib 5 import json 6 import pandas as pd 7 import time 8 import random 9 import re 10 from datetime import datetime 11 from lxml import etree
爬取字段确定
首先,我们只管的浏览用户主页,点击全部微博,观察我们能获取到的信息:
- 用户id
- 微博id
- 微博时间
- 微博内容
- 微博发布平台
- 微博评论数
- 微博点赞数
- 微博转发数
- 原微博id
- 原微博用户id
- 原微博用户名
- 原微博内容
- 原微博评论数
- 原微博点赞数
- 原微博转发数
然后,我们利用Chrome的developer tools观察用户个人主页所能获取到的主要内容,发现有些转发内容如果过长,无法直接通过用户主页进行爬取,而需要点进该条微博链接,对原微博进行爬取。
因此,我们可以爬取原微博的url,通过解析原微博url的内容来获取原微博的具体内容。
最终,通过综合情况,最后确定的字段为:
- 用户id——uid
- 微博id——mid
- 微博时间——time
- 微博发布平台——app_source
- 微博内容——content
- 微博评论数、点赞数、转发数——others
- 微博地址——url
- 是否转发——is_repost
- 原微博id——rootmid
- 原微博用户id——rootuid
- 原微博名——rootname
- 原微博地址——rooturl
加载页包抓取
在对用户的微博内容进行爬取时,最为困难的是解决网页加载的问题。微博需要两次加载,才能载入微博的全部内容,并进入下一页,因此,如何抓取到加载页的包是我们工作中最为重要的部分。
这里,我们需要借助Chrome的开发者工具,抓取页面加载时出现的包
发现加载的时间段中,出现了一个xhr类型的文件,长得最像我们需要的加载包:
https://weibo.com/p/aj/v6/mblog/mbloglist?ajwvr=6&domain=100505&profile_ftype=1&is_all=1&pagebar=0&pl_name=Pl_Official_MyProfileFeed__22&id=1005051956890840&script_uri=/p/1005051956890840/home&feed_type=0&page=1&pre_page=1&domain_op=100505&__rnd=1517384223025
再加载一次试验一下,发现出现它又出现了:
https://weibo.com/p/aj/v6/mblog/mbloglist?ajwvr=6&domain=100505&profile_ftype=1&is_all=1&pagebar=1&pl_name=Pl_Official_MyProfileFeed__22&id=1005051956890840&script_uri=/p/1005051956890840/home&feed_type=0&page=1&pre_page=1&domain_op=100505&__rnd=1517384677638
看到pl_name=Pl_Official_MyProfileFeed__22基本上就准了,为了保险起见,我们再点开链接看看,然后发现——果然是熟悉的配方,熟悉的味道~~
仔细解析这段url,发现:
- is_all是页面属性,表示全部微博
- page和pre_page都表示页数
- id是用户id【uid】和domain【100505】的结合体
- script_uri是当前用户的主页url字段
- pagebar长得最像加载页,第一个加载页为0,第二个加载页为1
- __rnd是时间戳,可以省略
结合初始网页没有pre_page和pagebar这两个字段,我们去掉这两个字段,运行一下url,观察一下所得到的内容,发现为加载前的用户发布的微博内容。
因此我们可以将用户主页的每一页分为三个部分,分成三个url进行解析,获取整个页面的内容。
具体代码如下:
1 # 初始化url 2 def getBeginURL(self): 3 begin_url = 'https://weibo.com/p/aj/v6/mblog/mbloglist?ajwvr=6&domain=100505&is_all=1&id=100505'+str(self.uid)+\ 4 '&script_uri=/u/1956890840&domain_op=100505&page=' 5 return begin_url 6 7 # 设置加载页url,并获取html内容 8 def getHTML(self,page_num,extend_part = ''): 9 # extend_part为获取加载页的扩展字段 10 url = self.getBeginURL()+str(page_num)+extend_part 11 data = urllib.request.urlopen(url).read().decode('utf-8') 12 html = json.loads(data)['data'] 13 return html 14 15 for x in range(3): 16 if x == 0: # 初始页面 17 extend_part = '' 18 elif x == 1: 19 b = x - 1 20 extend_part = '&pre_page=' + str(i) + '&pagebar=' + str(b) 21 elif x == 2: 22 b = x - 1 23 extend_part = '&pre_page=' + str(i) + '&pagebar=' + str(b) 24 html = self.getHTML(i, extend_part) 25 page = etree.HTML(html)
以上,最为头大的问题就解决啦~
博主写代码的时候为了解决加载包的问题头疼了好几天,结果发现Chrome的开发者工具比我想的还要强大的多,不由的感叹自己的愚蠢。发现了加载包的规律有,后面的一切都水到渠成,迅速的完成了微博内容爬取的代码~
下面是我的代码,各位可以参考。
博主为了可以实时更新内容,设置了爬取微博的时间段,可以避免每次都爬取页数而造成微博重复爬取的麻烦。
代码还有很多需要改进的地方,希望各位多多交流~
1 import urllib.error 2 import urllib.request 3 import urllib.parse 4 import urllib 5 import json 6 import pandas as pd 7 import time 8 import random 9 import re 10 from datetime import datetime 11 from datetime import timedelta 12 from lxml import etree 13 14 class getWeiboContent(): 15 """ 16 微博内容爬取: 17 mid 18 time 19 app_source 20 content 21 url 22 others(repost, like, comment) 23 is_repost 24 rootmid 25 rootname 26 rootuid 27 rooturl 28 """ 29 def __init__(self, uid, begin_date=None, begin_page=1, interval=None, flag=True): 30 self.uid = uid # 微博用户ID 31 self.begin_page = begin_page # 起始页 32 self.interval = interval # 需要爬取的页数,默认为None 33 self.begin_date = begin_date # 爬取的微博的起始发布日期,默认为None 34 self.flag = flag 35 36 # 初始化url 37 def getBeginURL(self): 38 begin_url = 'https://weibo.com/p/aj/v6/mblog/mbloglist?ajwvr=6&domain=100505&is_all=1&id=100505'+str(self.uid)+\ 39 '&script_uri=/u/1956890840&domain_op=100505&page=' 40 return begin_url 41 42 # 设置加载页url,并获取html内容 43 def getHTML(self,page_num,extend_part = ''): 44 url = self.getBeginURL()+str(page_num)+extend_part 45 print(url) 46 data = urllib.request.urlopen(url).read().decode('utf-8') 47 html = json.loads(data)['data'] 48 return html 49 50 # 爬取每条微博的内容,输出字典 51 def getContent(self,node): 52 dic = {} 53 dic['mid'] = node.xpath('./@mid')[0] 54 print('mid:'+dic['mid']) 55 dic['time'] = node.xpath('.//div[@class="WB_from S_txt2"]/a[1]/@title')[0] 56 app_source = node.xpath('.//div[@class="WB_from S_txt2"]/a[2]/text()') 57 if len(app_source) !=0 : # 部分微博不显示客户端信息 58 dic['app_source'] = app_source[0] 59 content = node.xpath('./*/*/div[@class="WB_text W_f14"]')[0].xpath('string(.)') 60 dic['content'] = re.compile('\n\s*(.*)').findall(content)[0] 61 others = node.xpath('.//ul[@class="WB_row_line WB_row_r4 clearfix S_line2"]//span[@class="line S_line1"]/span/em[2]/text()') 62 dic['repost_num'] = others[1] 63 dic['comment_num'] = others[2] 64 dic['like_num'] = others[3] 65 detail_info = node.xpath('./div[@class="WB_feed_handle"]/div/ul/li[2]/a/@action-data')[0] 66 dic['url'] = re.compile('&url=(.*?)&').findall(detail_info)[0] 67 rootmid = node.xpath('./@omid') 68 # 判断是否存在转发微博 69 if len(rootmid) != 0: 70 dic['is_repost'] = 1 71 dic['rootmid'] = rootmid[0] 72 weibo_expend = node.xpath('./*/*/div[@class="WB_feed_expand"]')[0] 73 rootname = weibo_expend.xpath('./*/*/a[@class="W_fb S_txt1"]/@nick-name') 74 # 判断原博是否被删除 75 if len(rootname) != 0: 76 dic['rootuid'] = re.compile('rootuid=(.*?)&').findall(detail_info)[0] 77 dic['rootname'] = re.compile('rootname=(.*?)&').findall(detail_info)[0] 78 dic['rooturl'] = re.compile('rooturl=(.*?)&').findall(detail_info)[0] 79 80 return dic 81 82 # 获取微博内容 83 def getWeiboInfo(self): 84 i = self.begin_page 85 # 判断是否划定了爬取页数 86 if self.interval is None: 87 # 若未划定爬取页数,则设置自动翻页参数hasMore=True 88 hasMore = True 89 end_page = False 90 else: 91 # 若划定爬取页数,则爬取页数优先 92 end_page = self.begin_page+self.interval 93 hasMore = False 94 # 初始化一个DataFrame用于存储数据 95 weibo_df = pd.DataFrame() 96 while (i <= end_page | hasMore) and self.flag: 97 for x in range(3): 98 if x == 0: # 初始页面 99 extend_part = '' 100 elif x == 1: # 第一个加载页 101 b = x-1 102 extend_part = '&pre_page=' + str(i) + '&pagebar=' + str(b) 103 elif x == 2: # 第二个加载页 104 b = x-1 105 extend_part = '&pre_page=' + str(i) + '&pagebar=' + str(b) 106 html = self.getHTML(i, extend_part) 107 page = etree.HTML(html) 108 if page is None: 109 break 110 else: 111 detail = page.xpath('//div[@class="WB_cardwrap WB_feed_type S_bg2 WB_feed_like "]') 112 # 判断用户是否发过微博 113 if len(detail) == 0: 114 print('该用户并未发过微博') 115 break 116 weibo = {} 117 weibo['mid'] = [] 118 weibo['time'] = [] 119 weibo['content'] = [] 120 weibo['app_source'] = [] 121 weibo['url'] = [] 122 weibo['repost_num'] = [] 123 weibo['comment_num'] = [] 124 weibo['like_num'] = [] 125 weibo['is_repost'] = [] 126 weibo['rootmid'] = [] 127 weibo['rootname'] = [] 128 weibo['rootuid'] = [] 129 weibo['rooturl'] = [] 130 for w in detail: 131 all_info = self.getContent(w) 132 # 判断是否设置了微博的开始日期 133 if self.begin_date is None: 134 pass 135 else: 136 weibo_dt = datetime.strptime(all_info['time'], '%Y-%m-%d %H:%M').date() 137 begin_dt = datetime.strptime(self.begin_date, "%Y-%m-%d").date() 138 # 判断微博发布日期是否在开始日期之后 139 if begin_dt > weibo_dt: 140 # 当微博发布日期在开始日期之后时,停止爬取 141 self.flag = False 142 break 143 weibo['mid'].append(all_info.get('mid', '')) 144 weibo['time'].append(all_info.get('time', '')) 145 weibo['app_source'].append(all_info.get('app_source','')) 146 weibo['content'].append(all_info.get('content', '')) 147 weibo['url'].append(all_info.get('url', '')) 148 weibo['repost_num'].append(all_info.get('repost_num', '')) 149 weibo['comment_num'].append(all_info.get('comment_num', '')) 150 weibo['like_num'].append(all_info.get('like_num', '')) 151 weibo['is_repost'].append(all_info.get('is_repost', 0)) 152 weibo['rootmid'].append(all_info.get('rootmid', '')) 153 weibo['rootname'].append(all_info.get('rootname', '')) 154 weibo['rootuid'].append(all_info.get('rootuid', '')) 155 weibo['rooturl'].append(all_info.get('rooturl', '')) 156 weibo = pd.DataFrame(weibo) 157 weibo['uid'] = self.uid 158 weibo_df = weibo_df.append(weibo,ignore_index=True) 159 # 提取下一页链接 160 if page is None: 161 break 162 else: 163 next_page = page.xpath('//a[@class="page next S_txt1 S_line1"]/@href') 164 if len(next_page) == 0: # 判断是否存在下一页 165 self.flag = False 166 print('已是最后一页') 167 else: 168 page_num = re.compile('page=(\d*)').findall(next_page[0])[0] 169 i = int(page_num) 170 time.sleep(random.randint(5, 10)) # 设置睡眠时间 171 return weibo_df 172 173 if __name__=='__main__': 174 uid = input('请输入uid:') 175 begin_date = input('请输入日期,格式为xxxx-xx-xx:') 176 begin_page = input('请输入开始页,默认为1:') 177 getWeiboContent(uid, begin_date).getWeiboInfo()