Python实现爬取移动端网页版微博用户信息及(部分)粉丝和(部分)关注信息(一)

4 篇文章 0 订阅
1 篇文章 0 订阅

电脑端网页版微博weibo.com的处理相对复杂,先从最简单的移动端weibo.cn开始。因为微博系统限制,移动端只能查看前20页关注和粉丝信息,所以对于关注或粉丝超过200的用户,只能获取部分粉丝和部分关注的信息。
用户主页的链接有3种形式

www.weibo.cn/uid
www.weibo.cn/u/uid
www.weibo.cn/个性域名

1. UID

用户主页F12开发者工具
所以打开用户主页的时候链接不一定含有UID,还需要重新获取。方法就是获取这个标签href的属性值

<a href="/1744395855/info">资料</a>

查找这一标签与一般的查找稍有不同,因为只能确定标签名是a,属性名是href,属性值的第一个字符是/,最后五个字符是/info,中间的数字正是我们要查找的内容。这是正则表达式表现的时间,恰好BeautifulSoup的查找函数支持正则表达式。

from bs4 import BeautifulSoup as bs
import regex # 正则表达式
def getUid(soup: bs):
	"""
	soup是用户主页html解析的结果,BeautifulSoup的实例
	"""
	addr = soup.find(name='a', attrs={'href', regex.compile(r"/\S*/info")})
	if addr:
		return addr['href'].split('/')[1]
	print('uid 查找失败')
	return None

2. 基本信息

进入用户资料页,URL很有特点:

weibo.cn/uid/info

进入资料页面的前提是得到uid,所以上一步非常重要。
在这里插入图片描述
资料页面包含了用户的基本信息和个性域名。页面显示的都是基本的文本,在同一个标签下,用<br>换行分隔。从基本信息的规律发现,信息的排版规律,均为(除了认证信息)“属性:属性值”的格式。利用find(text=*)函数可以查找对应文本。所以定义一个函数获得这些内容(除生日,需特殊处理)。

basicInfoType = {'nickname': '昵称',
                 'identity': '认证',
                 'sex': '性别',
                 'location': '地区',
                 'description': '简介'
                }
def getBasicInfo(soup: bs, infoType: str) -> str:
    """
    soup是信息页html解析返回结果,BeautifulSoup的实例
    infoType: 昵称, 认证, 性别, 地区, 简介
    """
    if infoType.lower() not in basicInfoType:
        raise ValueError('wrong basic infomation type\n' + str(basicInfoType)) 
    pattern = basicInfoType[infoType] + ':*'
    infoSection = soup.find(text=regex.compile(pattern))
    if infoSection:
        info = infoSection.split(':')[1:]
        basicInfo = ''
        for item in info:
            basicInfo += item
        return basicInfo
    return ''

这里也用了正则表达式来匹配一行文字。
在这一页中,获得文本的方法基本相同,仅仅是一些项要多处理一次。个性域名信息与前面方法一致——找到个性域名URL所在的行,对文本进行“/”分割。字符串"手机版:https://weibo.cn/个性域名"进行“/”分割后得到列表[“手机版https:”, “”, “weibo.cn”, “个性域名”],个性域名是该列表的最后一个元素,用索引-1取出即可。实际上,假如一个用户没有设置个性域名,那么在“其他信息”这一栏将会出现:

电脑版:http://weibo.com/u/uid
手机版:https://weibo.cn/u/uid

这种情况下,分割字符串得到的列表元素个数为5。因此,正确获得个性域名的前提是列表元素个数是4。

def getCustomDomain(soup):
    """
    soup是信息页html解析返回结果,BeautifulSoup的实例
    """
    addr = soup.find(text=regex.compile(r"手机版:https://weibo.cn/*"))
    #print(addr)
    if addr:
        if len(urlSection := addr.split('/')) == 4:
            return urlSection[-1]
    return ''

NOTE: 上面的代码使用了“:=”运算符。“:=”运算符称作海象运算符(walrus operator),是Python 3.8的新特性。海象运算符不仅能让代码更简洁和增加可读性,还能提高运算速度。该运算符的作用是把右边表达式的值赋值给左边的变量,左边的变量还能进行下一步运算。以判断列表元素是否大于5,如果大于5则输出元素个数为例

# a是一个列表
# 以前的写法
n = len(a)
if n > 5:
	print(n)

# 或者
if len(a) > 5:
	print(len(a))

# 海象运算符的写法
if (n := len(a)) > 5:
	print(n)

例子说明应用海象运算符可以减少代码或避免重复调用。另一个有趣的例子是

print((a:=(1+(b:=2+(c:=3+(d:=4+(e:=5+6))))))<5) # False
print(a,b,c,d,e) # 21 20 18 15 11

获得出生日期的方法基本相同,不过需要添加额外的判断,简单判断是否是有效的日期,判断日期形式(年、年-月、年-月-日、月-日)

def getBirthdate(soup):
    """
    soup是信息页html解析的返回结果,BeautifulSoup的实例
    """
    date = soup.find(text=regex.compile(r"生日:*"))
    if date:
        dateBlock = date.split(':')[-1].split('-')
        if len(dateBlock) == 3:
            return int(dateBlock[0]), int(dateBlock[1]), int(dateBlock[2])
        if len(dateBlock) == 2:
            return int(dateBlock[0]), int(dateBlock[1])
        if len(dateBlock) == 1:
            return int(dateBlock[0])
    return None

主页和资料页信息抓取代码如下

def getUserHomepageInfo(person, mobile=False): 
	# mobile用来注明是否是移动端网页
    homeURL = homepageUrl(person, mobile)
    print(homeURL)
    soup = getHtml(url=homeURL, headers=header)
    
    if uid := getUid(soup):
        person.uid = uid  
    person.realFansNum = soup.find(name='a', attrs={'href': '/'+person.oid+'/fans'}).text.split('[')[1][:-1]
    person.realFocusNum = soup.find(name='a', attrs={'href': '/'+person.oid+'/follow'}).text.split('[')[1][:-1]
    infoURL = infoPageUrl(person)
    infoSoup = getHtml(url=infoURL, headers=header)
    if (customDomain := getCustomDomain(infoSoup)):
        person.CustomDomain = customDomain
    if (date := getBirthdate(infoSoup)):
        try:
            if len(date) == 3:
                person.birthYear, person.birthMonth, person.birthDay = date
            if len(date) == 2:
                if date[0] > 12:
                    person.birthYear, person.birthMonth = date
                else:
                    person.birthMonth, person.birthDay = date
            if len(date) == 1:
                person.birthYear = date
        except:
            pass
    if (name := getBasicInfo(infoSoup, 'nickname')):
        person.name = name
    if (identity := getBasicInfo(infoSoup, 'identity')):
        person.identity = identity
    if (sex := getBasicInfo(infoSoup, 'sex')):
        person.sex = sex
    if (description := getBasicInfo(infoSoup, 'description')):
        person.description = description
    if (location := getBasicInfo(infoSoup, 'location')):
        person.location = location
    
    print(person.name + " 主页信息抓取成功\n-----------------------------------------------")

3. 关注和粉丝

关注和粉丝信息的页面结构相同,所以只要写一个函数就可以完成两个类似的任务。首先分析URL的特点,打开任意一个移动端网页版微博的关注页和粉丝页。关注URL,有以下两种形式

关注:  https://weibo.cn/uid/follow?page=页码 
粉丝:  https://weibo.cn/uid/fans?page=页码

很容易就能打开相应的页面。再来看系统允许我们查看多少页
在这里插入图片描述
写一个函数获得可以迭代的次数

def getPageNum(soup):
	"""
	soup是关注页或粉丝页html的解析的返回结果,BeautifulSoup的实例
	"""
    return int(soup.find(name='input', attrs={'name': 'mp'})['value'])

同样地,F12检查
在这里插入图片描述
每一个用户的主页链接出现两次(红色方框),并且父标签都是<td>、属性都是valign=“top”,所以每个关注(粉丝)用户信息出现两次。获取的方法比较简单,用findAll函数找出所有标签,后以step=2逐个提取。从红色方框中也能看到,用户主页URL不一定包含UID(优先展示个性域名),所以第一步获得UID很重要。其实这里使用了一种麻烦的方法,因为每条信息都出现两次。观察图中第一个<td>标签,它还有style="width: 52px"属性。还有一个更简单的方案是蓝色方框的内容,这个表情是“关注他”按钮的链接,每个用户只出现一次,而且链接中已经包含了uid。如果利用这个标签的信息,第一步获取uid就是不必要的。
利用红色方框标签信息代码

def getRelation(person, relation, mobile=False):
    if not (relation := relation.lower()) in ['focus', 'fans']:
        raise ValueError('参数必须是 [\'focus\',\'fans\']')
    if relation == 'focus':
        pageUrlFunc = focusPageUrl
        addFunc = person.addFocus
    else:
        pageUrlFunc = fansPageUrl
        addFunc = person.addFans
    url = pageUrlFunc(person, 1, mobile)
    soup = getHtml(url=url, headers=header)
    pageNum = getPageNum(soup)
    for page in range(1, pageNum+1):
        url = pageUrlFunc(person, page, mobile)
        soup = getHtml(url=url, headers=header)

        blank = ' ' if page < 10 else ''
        print('正在抓取第', str(page)+blank, '页信息')
        
        memberList = soup.findAll(name='td', attrs={'valign': 'top'})
        for i in range(1, len(memberList), 2):
            memberInfo = memberList[i].find(name='a')
            name = memberInfo.text
            uid = memberInfo['href'].split('/')[-1]
            addFunc(WeiboUser(name=name, uid=uid))
    print(person.name + ': ' + relation + " 信息抓取成功\n-------------------------------------------")

4. 基本链接和HTML

def homepageUrl(person, mobile=False):
    if mobile:
        pofix = ''
        if person.uid:
            pofix = person.uid
        elif person.customDomain:
            pofix = person.customDomain
        else:
            raise RuntimeError('缺少必要信息')
        return 'https://weibo.cn/' + pofix
    
    if oid := person.oid:
        return 'https://www.weibo.com/u/' + oid
    if pid := person.pageId:
        return 'https://www.weibo.com/p/' + pid
    if domain := person.customDomain:
        return 'https://www.weibo.com/' + domain
    
def focusPageUrl(person, page=1, mobile=False):
    if mobile:
        return 'https://weibo.cn/' + person.uid + '/follow?page=' + str(page)
    return 'https://weibo.com/p/' + person.pageId + '/follow?page=' + str(page)

def fansPageUrl(person, page=1, mobile=False):
    if mobile:
        return 'https://weibo.cn/' + person.uid + '/fans?page=' + str(page)
    return 'https://weibo.com/p/' + person.pageId + '/follow?relate=fans&page=' + str(page)

def getHtml(url, headers):
    response = requests.get(url=url, headers=headers)
    if (html := response.text):
        return bs(html, 'lxml')
    print('无内容,正在重新请求')
    getHtml(url, headers)

getHtml函数接收URL和请求头,返回经过BeautifulSoup实例。因为不登录微博,我们看不到用户的关注,所以请求头里应该包含登录信息,cookie正是包含登录信息的一项。打开浏览器登录微博,按F12进入开发者工具,选中网络(Network)
在这里插入图片描述
把cookie所有内容保存下来。为了模拟浏览器浏览,把user-agent也保存下来。

cookie = '**************'
userAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) ****'
header = {'User-Agent': userAgent,
          'cookie': cookie
         }

除此之外,getHtml函数还可能进入递归。有时候因为各种原因,请求没有响应。getHtml函数调用自身相当于可以多请求几次直到有返回结果。但是没必要一直请求(python也不允许一直递归,不能超过递归深度),设置递归深度可以实现这一功能,在文件的开头设置

import sys
sys.setrecursionlimit(10) #设置递归深度

在程序最后,恢复默认递归深度(998)。(递归深度10时,matplotlib包导入失败)

sys.setrecursionlimit(998)

高频地请求可能会导致访问被限制,在getHtml函数里添加命令,使每次请求前暂停一段时间,模拟人的操作。

import time
pauseTime = 1 # 1秒
def getHtml(url, headers):
	# *** 其他 ***
	time.sleep(pauseTime)
	# *** 其他 ***

5. 数据保存

使用类作为用户的模板可以提高代码的可读性,每创建一个用户的时候只要创建一个实例。准备工作已经实现微博用户类的创建。

6. 完整代码

import requests
from bs4 import BeautifulSoup as bs
from Person import WeiboUser # 准备工作
import regex
import time
import sys
import os

sys.setrecursionlimit(10) # 设置递归深度,不必多次请求同一个页面
pauseTime = 1

basicInfoType = {'nickname': '昵称',
                 'identity': '认证',
                 'sex': '性别',
                 'location': '地区',
                 'description': '简介'
                }
cookie = '************************************'

userAgent = '********************************'
header = {'User-Agent': userAgent,
          'cookie': cookie
         }

def homepageUrl(person, mobile=False):
    if mobile:
        pofix = ''
        if person.uid:
            pofix = person.uid
        elif person.customDomain:
            pofix = person.customDomain
        else:
            raise RuntimeError('neccessary information is needed')
        return 'https://weibo.cn/' + pofix
    
    if oid := person.oid:
        return 'https://www.weibo.com/u/' + oid
    if pid := person.pageId:
        return 'https://www.weibo.com/p/' + pid
    if domain := person.customDomain:
        return 'https://www.weibo.com/' + domain
    
def focusPageUrl(person, page=1, mobile=False):
    if mobile:
        return 'https://weibo.cn/' + person.oid + '/follow?page=' + str(page)
    return 'https://weibo.com/p/' + person.pageId + '/follow?page=' + str(page)

def fansPageUrl(person, page=1, mobile=False):
    if mobile:
        return 'https://weibo.cn/' + person.oid + '/fans?page=' + str(page)
    return 'https://weibo.com/p/' + person.pageId + '/follow?relate=fans&page=' + str(page)

def getHtml(url, headers):
    time.sleep(pauseTime)
    response = requests.get(url=url, headers=headers)
    if (html := response.text):
        return bs(html, 'lxml')
    print('无内容,正在重新请求')
    getHtml(url, headers)

def getInfoFromText(soup, tagName, attrs):
    return soup.find(name=tagName, attrs=attrs).text

def getInfoFromAttr(soup, searchTagName, searchAttr, targetedAttrName):
    return soup.find(name=searchTagName, attrs=searchAttr)[targetedAttrName].text

def getPageNum(soup):
    return int(soup.find(name='input', attrs={'name': 'mp'})['value'])

def getUid(soup):
    addr = soup.find(name='a', attrs={'href': regex.compile(r"\S*/info")})
    print(addr)
    if addr:
        return addr['href'].split('/')[1]
    print('uid查找失败 跳过')
    return None

def infoPageUrl(person):
    return 'https://weibo.cn/' + person.uid + '/info'

def getCustomDomain(soup):
    """
    soup: html of info page
    """
    addr = soup.find(text=regex.compile(r"手机版:https://weibo.cn/*"))
    #print(addr)
    if addr:
        if len(urlSection := addr.split('/')) == 4:
            return urlSection[-1]
    return ''

def getBirthdate(soup):
    """
    html of info page
    """
    date = soup.find(text=regex.compile(r"生日:*"))
    if date:
        dateBlock = date.split(':')[-1].split('-')
        if len(dateBlock) == 3:
            return int(dateBlock[0]), int(dateBlock[1]), int(dateBlock[2])
        if len(dateBlock) == 2:
            return int(dateBlock[0]), int(dateBlock[1])
        if len(dateBlock) == 1:
            return int(dateBlock[0])
    return None

def getBasicInfo(soup, infoType):
    """
    html of info page
    infoType: 昵称, 认证, 性别, 地区, 简介
    """
    if infoType.lower() not in basicInfoType:
        raise ValueError('wrong basic infomation type\n' + str(basicInfoType)) 
    pattern = basicInfoType[infoType] + ':*'
    infoSection = soup.find(text=regex.compile(pattern))
    if infoSection:
        info = infoSection.split(':')[1:]
        basicInfo = ''
        for item in info:
            basicInfo += item
        return basicInfo
    return ''
 
def getUserHomepageInfo(person, mobile=False): 
	# mobile用来注明是否是移动端网页
    homeURL = homepageUrl(person, mobile)
    print(homeURL)
    soup = getHtml(url=homeURL, headers=header)
    
    if uid := getUid(soup):
        person.uid = uid    
    person.realFansNum = soup.find(name='a', attrs={'href': '/'+person.oid+'/fans'}).text.split('[')[1][:-1]
    person.realFocusNum = soup.find(name='a', attrs={'href': '/'+person.oid+'/follow'}).text.split('[')[1][:-1]
    infoURL = infoPageUrl(person)
    infoSoup = getHtml(url=infoURL, headers=header)
    if (customDomain := getCustomDomain(infoSoup)):
        person.CustomDomain = customDomain
    if (date := getBirthdate(infoSoup)):
        try:
            if len(date) == 3:
                person.birthYear, person.birthMonth, person.birthDay = date
            if len(date) == 2:
                if date[0] > 12:
                    person.birthYear, person.birthMonth = date
                else:
                    person.birthMonth, person.birthDay = date
            if len(date) == 1:
                person.birthYear = date
        except:
            pass
    if (name := getBasicInfo(infoSoup, 'nickname')):
        person.name = name
    if (identity := getBasicInfo(infoSoup, 'identity')):
        person.identity = identity
    if (sex := getBasicInfo(infoSoup, 'sex')):
        person.sex = sex
    if (description := getBasicInfo(infoSoup, 'description')):
        person.description = description
    if (location := getBasicInfo(infoSoup, 'location')):
        person.location = location
    
    print(person.name + " 主页信息抓取成功\n-----------------------------------------------")

def getRelation(person, relation, mobile=False):
    if not (relation := relation.lower()) in ['focus', 'fans']:
        raise ValueError('argument relation must be in [\'focus\',\'fans\']')
    if relation == 'focus':
        pageUrlFunc = focusPageUrl
        addFunc = person.addFocus
    else:
        pageUrlFunc = fansPageUrl
        addFunc = person.addFans
    url = pageUrlFunc(person, 1, mobile)
    soup = getHtml(url=url, headers=header)
    pageNum = getPageNum(soup)
    for page in range(1, pageNum+1):
        url = pageUrlFunc(person, page, mobile)
        soup = getHtml(url=url, headers=header)

        blank = ' ' if page < 10 else ''
        print('正在抓取第', str(page)+blank, '页信息')
        
        memberList = soup.findAll(name='td', attrs={'valign': 'top'})
        for i in range(1, len(memberList), 2):
            memberInfo = memberList[i].find(name='a')
            name = memberInfo.text
            uid = memberInfo['href'].split('/')[-1]
            addFunc(WeiboUser(name=name, uid=uid))
    print(person.name + ': ' + relation + " 信息抓取成功\n-------------------------------------------")

def userInformation(person, mobile=True):
    getUserHomepageInfo(person, mobile)
    getRelation(person, 'focus', mobile)
    getRelation(person, 'fans', mobile)

def test(person, func, mobile=True):
    url = func(person, mobile=mobile)
    return getHtml(url, header)

lijian = WeiboUser(uid = '1744395855')
userInformation(lijian)

sys.setrecursionlimit(998)

尽管如此,有时候访问还是被限制。暂停时间设置为2秒或以上使得获取数据很慢。在技术上应该可以通过以下方式改进:

  1. 使用代理。不断地更换ip
  2. 使用多个账号。不断更换cookie
  3. 更换User-Agent。不断更改user-agent,fakeuseragent包提供该功能。

7. 测试

在这里插入图片描述
从获得的199个粉丝数据中统计性别比例,其中有130个信息填写为女性
在这里插入图片描述
说明在最新的199个粉丝中,女性粉丝占比比较大。
在这里插入图片描述
从199个粉丝中统计填写了生日并且出生年份在1970-2005的人数,00-04年龄段最多,号称90后和号称00后的最多。但在这组统计中只有81个有效的数据。
在这里插入图片描述

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
### 回答1: 要想指定时间的微博,可以使用Python语言,借助第三方库来实现。 首先,我们需要登录微博账号,可以使用Selenium库来模拟浏览器登录账号。登录成功后,可以使用BeautifulSoup库从页面中解析出需要的数据。 接着,我们需要构造搜索关键字和时间段。可以使用urlencode函数将关键字和时间段拼接成URL参数,并使用requests库发起请求。在发送请求时,可以设置请求头信息以模拟浏览器请求。 当接收到响应后,我们需要从页面中解析出微博内容。可以使用正则表达式或BeautifulSoup库解析HTML,从中提出需要的信息。 最后,将到的微博内容存储到本地文件或数据库中,方便后续处理和分析。 需要注意的是,微博虫行为有一定的限制,如果频繁访问同一链接,可能会触发微博的反机制,导致IP被封禁,因此虫过程中需要注意控制频率。 ### 回答2: 要使用Python指定时间的微博,我们可以通过以下步骤完成。 首先,我们需要安装相关的Python库。我们可以使用Python库中的requests库来发送HTTP请求获网页内容,使用BeautifulSoup库来解析网页内容,以及使用selenium库来模拟浏览器行为。 接下来,我们需要登录微博账号来获相应的权限。我们可以使用selenium库来模拟用户登录微博,并保存登录后的cookies。 然后,我们可以使用requests库发送HTTP请求来获特定时间范围内的微博页面内容。我们可以构造适当的URL,并使用保存的cookies来验证权限。获到的内容可以通过BeautifulSoup库进行解析。 最后,我们可以从解析的页面中提出我们想要的信息,例如微博的内容、用户信息等等。我们可以使用正则表达式或BeautifulSoup库来定位和提相应的信息。 综上所述,凭借Python中的相关库,我们可以很方便地实现指定时间的微博的功能。 ### 回答3: 要用Python指定时间的微博,首先需要了解微博方式。微博网页的URL结构可以通过观察分析得知,通过模拟请求这些URL,我们就可以获指定时间的微博内容。 接下来,我们可以使用Python虫框架Scrapy来实现微博。首先,我们需要创建一个Scrapy项目,然后在该项目的`spiders`文件夹中创建一个虫文件。 在虫文件中,我们需要定义`start_requests`方法来发送请求。我们可以使用`start_urls`来指定微博页面的URL,同时可以传递参数来指定时间段。可以通过修改URL的查询参数来实现到的微博内容可以通过解析网页的HTML文档来获。使用XPath或BeautifulSoup等库可以方便地提微博的内容、发布时间等信息。可以通过相应的选择器来定位HTML中的微博元素,然后提相关信息。 最后,我们可以将微博内容保存到数据库或文件中,以便进一步处理和分析。可以使用Python的数据库库或文件操作库来实现。 在编写虫代码时,需要注意尊重网站的规则,不要对网站造成过大的访问压力。可以适当设置请求的间隔时间,并且避免同时进行过多的并发请求。 总之,通过以上的步骤,我们可以使用Python指定时间的微博,并且提出所需的内容。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值