selenium爬取QQ空间

这几天在看《从零开始学python网络爬虫》中的模拟浏览器篇,对其中的爬取好友说说比较感兴趣,不过书中只是爬取每个好友第一页说说,因此我稍微改进了下(发书名是尊重作者,不过个人认为这本书讲得比较浅,不求甚解)。

先大致说一下我遇到的坑。首先,如果想要看别人的说说,是必须要登录的(使用cookie应该也可以);然后,可能没有权限访问好友空间;最后则是获取下一页链接并点击前还要注意可能没有下一页了。

本次使用的是selenium和Chrome,书中使用的是PlantomJS,为什么不使用这个呢?看下图:

上图说的是PhantomJS已经废弃了,不建议使用,可以选用Chrome或FireFox浏览器的无界面模式。记得前几天逛csdn时有人说一开始只有PhantomJS支持selenium的,自从其他大公司支持后,老伙伴PhantomJS就没落了。。。

1.获取QQ通讯列表

QQ邮箱中可以导出好友邮箱。打开QQ邮箱中的通讯录:

之后的“工具”|“导出联系人”,选择以csv格式导出:

 之后下载该csv即可。该文件字段大致如下:

 我们这里主要用到的是“姓名”和“电子邮件”。

2.大致流程

因为有了QQ邮箱的存在,所以我们可以通过QQ邮箱来反推得到QQ号,不过需要注意的是,并不是所有的电子邮箱都是QQ邮箱。分析一下QQ邮箱的特征:

QQ号@qq.com

嗯~,qq号都是数字,那么可以使用正则表达式来提取出QQ号,具体实现如下:

def get_qq_numbers(filename):
    '''
    从csv文件中获取正确的qq号
    并yield
    '''
    #正则匹配出qq号
    pattern = re.compile('(.*?)@qq\.com')

    fp = open(filename, 'r')
    reader = csv.DictReader(fp)

    #获取每个用户的电子邮箱
    for row in reader:
        name = row['姓名']
        email = row['电子邮件']
        qqNumber = re.search(pattern, email)

        if qqNumber:
            yield qqNumber.group(1), name
        else:
            logging.warning('该邮箱不是QQ号: %s %s' % (name, email))
    fp.close()

get_qq_number()是读取刚才导出的csv文件,然后获取“电子邮件”中的值,并使用正则表达式进行提取,如果提起成功,则表示该邮箱是QQ邮箱,那么得到的一般情况下是qq号。

要导入的包如下:

import csv
import re
import logging
import json
import selenium
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

import config
logging.basicConfig(level=logging.WARNING,#控制台打印的日志级别
                    filename='new.log',
                    filemode='w',
                    format=
                    '%(asctime)s: %(message)s'
                    )
options = webdriver.ChromeOptions()
#无图模式
prefs = {
    'profile.default_content_setting_values': {
        'images': 2
    }
}
options.add_experimental_option('prefs', prefs)
#浏览器隐藏
options.add_argument('--headless')

browser = webdriver.Chrome(chrome_options = options)
wait = WebDriverWait(browser, 5)
browser.implicitly_wait(3)

首先配置了logging,捕捉WARNING及以上并写入到new.log文件。

之后设置Chrome不显示图片,并且隐藏。

最后创建了一个浏览器,并同时设置了隐式等待和显示等待,个人认为两者区别不大,当没有获得到对应的节点时都会抛出异常;不过显示等待相对比较灵活,可以选择等待条件。

先看一下总调用(额,主函数?):

if __name__ == '__main__':
    #限定爬取个数
    start = 1
    end = 5

    notes = []

    for qq, name in get_qq_numbers('./QQmail.csv'):
        L = {}
        L['qq'] = qq
        L['name'] = name
        L['notes'] = []

        print(f'crawling {qq} {name}')
        for data in get_info(qq, name, 3):
            #pprint.pprint(data)
            L['notes'].append(data)

        if len(L['notes']) != 0:
            notes.append(L)
        #限制爬取数目
        start += 1
        if start > end:
            break

    #写入文件
    fp = open('notes.json', 'w', encoding = 'utf-8')
    fp.write(json.dumps(notes, indent = 2, ensure_ascii = False))
    fp.close()

    browser.close()

这里面限定了爬取的个数,可以根据要求自行修改。上述代码的逻辑大致如下:

获取csv文件中QQ号和对应的昵称;然后get_info()来获取好友的若干页说说(这里指定为3页,不指定则爬取所有说说);接着判断L中的notes列表是否为空,当该好友的空间没有权限访问时,get_info()返回None,因此需要判断是否为空,不为空则添加。最后把所有的信息写入文件即可。

3.访问QQ空间

前面的代码中并没有涉及到HTML,而接下来的get_info中大部分要和HTML打交道。首先,下面的函数来判断是否已经登录:

def is_logining(browser):
    '''
    判断是否需要登录
    @param browser webdriver的一个实例
    @return True 表示在登录中
    '''
    try:
        browser.find_element(By.ID, 'login_frame')
        ret = False
    except:
        ret = True
    return ret

login_frame为https://i.qq.com/中的一个标签的id名,因此可以判断这个标签是否存在来判断是否需要登录,注意这里用的是隐式等待,在前面已经调用过下面这一句:

browser.implicitly_wait(3)

这个函数的说明如下:

 从上面可知,在一次会话中只需要被调用一次即可(我看的那本书里面调用了这个函数多次),调用后应该会对之后的每一次非显式等待查找生效。

之后是登录函数:

def login(browser, username, passwd):
    '''
    QQ登录
    @param browser 浏览器 webdriver的一个实例
    @param username 登录账号
    @param passwd 登录密码
    '''
    browser.switch_to.frame('login_frame')
    switcher = browser.find_element(By.ID, 'switcher_plogin')
    switcher.click()

    #输入账号和密码
    user_input = browser.find_element(By.ID, 'u')
    user_input.clear()
    user_input.send_keys(username)

    passwd_input = browser.find_element(By.ID, 'p')
    passwd_input.clear()
    passwd_input.send_keys(passwd)

    login_btn = browser.find_element(By.ID, 'login_button')
    login_btn.click()

login负责输入账号和密码,然后点击登录。

上面两个函数都是要在get_info中使用。

def get_info(qq, name = None, maxPage = None):
    '''
    传入qq号,爬取其说说
    '''
    url = f'https://user.qzone.qq.com/{qq}/311'
    browser.get(url)
    #是否已经登录
    logining = is_logining(browser)

    #登录
    if logining == False:
        login(browser, config.username, config.passwd)

    #是否允许访问
    try:
        browser.find_element(By.ID, 'QM_OwnerInfo_Icon')
        permission = True
    except:
        permission = False
    #允许访问 第一次需要切换frame
    if permission == True:
        browser.switch_to.frame('app_canvas_frame')

首先根据QQ号码生成url,之后访问该链接,然后判断是否需要登录;最后则通过查看标签的方式来判断是否有权限来访问QQ空间,另外用户名和密码存入了config.py这个文件中,自行修改即可。

    #循环获取说说
    index = 1
    while permission == True and (maxPage == None or index <= maxPage):
        #获取下一页按钮
        try:
            next_btn = wait.until(EC.presence_of_element_located( \
                    (By.CSS_SELECTOR, f'#pager_next_{index - 1}')))
        #获取失败,没有下一页
        except selenium.common.exceptions.TimeoutException as e:
            #退出循环条件
            maxPage = index

        #获取说说内容和发表时间
        contents = browser.find_elements(By.CSS_SELECTOR, '.content')
        times = browser.find_elements(By.CSS_SELECTOR, '.c_tx.c_tx3.goDetail')

        print('crawling page', index)
        for content, tim in zip(contents, times):
            data = {
                    'time' : tim.text,
                    'content': content.text,
                    }
            yield data
        #下一页
        index += 1
        if index <= maxPage:
            next_btn.click()
    #不可访问页面
    if permission == False:
        logging.warning('该用户不允许你访问 %s %s' % (name, qq))
        print('该用户不允许你访问 %s %s' % (name, qq))

接下来的循环中就是获取该页面的所有说说和发表日期。首先,先显式等待获取到下一页按钮,如果超时,则表示没有下一页,此时获取该页面的说说后退出即可;内容和时间都是通过find_elements方法获取到的,它无法做到像xpath、beautiful soup那样灵活,如果想要获取每个说说下的评论的话,就需要修改成使用xpath等来解析页面。之后返回每个说说和对应的时间。

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值