注:这只是很多方法中的一种,当然也不是最好的一种,有其他好的方法,希望大家可以在评论区交流学习

1.需要爬取的数据

用户主页的Name、ID、Introduction、以及用户关注的Following的用户的同样信息。
image

2.遇到的问题

twitter的用户的following用户界面使用的动态加载的方式,并非静态的HTML界面,用户的Following用户的信息根据滚轮滑动动态进行加载,所以使用selenium中findf_elenment()的方法不能对需要爬取的信息进行定位。
在这里插入图片描述

最后的解决方案:使用selenium的中webdriver模拟滚轮滑动,抓取response中所有带有Following的数据包,对抓取到的包进行解析,最终得到我们想要的数据。
在这里插入图片描述

将response中的数据复制下来,就是我们需要的Following的信息数据。Response中的数据如下图所示:
在这里插入图片描述

每一个entryId就是一个Following的详细数据,只要能把response包抓下来,那一切是不是都能解决了,所以我们接下来的问题就是如何在使用selenium的webdriver模拟的浏览器的同时将数据包抓下来进行数据解析。
注:该方法的主要缺点是和网速有很大关系,本来selenium的爬取速度就已经令人捉急,现在为了模拟滚轮滑动有很多强制sleep()操作,整个程序的运行效率并不是很高,这也是后面需要改进的地方。

3.尝试过的工具

requests:无法使用cookie登录twitter,也就无法进行数据爬取,遂放弃。
scrapy:使用一次cookie登录之后,后面就没有办法再登录了,遂放弃。
mitmproxy:中间代理,用于抓取response数据包,配置太复杂,遂放弃。
browsermobproxy:针对http网站的数据转包可行,但是Twitter需要https,需要先设置FQ代理,没有办法设置俩个代理,遂放弃。
requestium:是requests和selenium的结合体,特点是使用cookie将driver转化为requests登录,可以加快爬虫速率,本质上还是requests使用cookie登录twitter,网络响应一直是443,没有办法进行连接,遂放弃。
selenium:使用webdriver模拟登陆twitter,获取用户主页的各种数据,点击Following界面并且模拟滚轮滚动,selenium的任务完成。
chrome devtool:Chrome的开发者工具,它可以获取driver运行中网络日志,我们根据网络日志获取resquestId,然后找到该requestId对应的response,最后对response中的数据进行解析,拿到我们需要的数据。

4.详细过程

获取登录的cookie
首先安装一个插件EditThisCookie,用于获取登录twitter之后的Cookie信息。接着登录你的twitter账号
在这里插入图片描述

接着如下图所示,点击EditThisCookie插件--->然后点击按钮2将cookie进行复制,保存在一个json文件中,为后面模拟登陆做准备。
在这里插入图片描述

1).模拟登陆

由于twitter的访问需要FQ,所以最好在爬虫之前保证自己的电脑有对应的软件。接着我们介绍如何使用selenium中的webdriver模拟twitter登陆。
在这里插入图片描述

注解:

  1. 你自己本地保存Cookie文件的位置。
  2. 设置的FQ代理。
  3. 需要打开Chrome用于获取network的设置,
  4. 和3的作用是一样的,但是我在Chrome v95的下尝试后发现,该操作会将selenium的所有操作转化为dict,无法进行正常的操作。
  5. 先在没有cookie的情况下get到url,然后加载了cookie之后,再尝试get,这样才能保证正常登录。
    在这里插入图片描述

很多之前的博客介绍打开Chrome的network工具的时候,要么只介绍4中提到的方法,要么将俩种方法都提及到,这个地方让我花了好长时间去弄清楚。

2).爬取主页信息

一般的推特用户的主页是"https://twitter/com/"+"uid",这个uid是指用户主页名字下面“@”后面的字符,所以只要拿到uid就可以访问用户主页。用户主页的格式比较固定,所以可以使用Xpath定位的方法进行数据爬取。
在这里插入图片描述

  1. Xpath可以在chrome 中点击需要查看的元素--->单击右键--->检查--->在源码区域再次右键单击--->Copy--->Copy Xpath,对于所有定位的元素都可以这样操作。
  2. webdriver中用于检测定位元素是否加载出来的方法,TIMEOUT是一个自己设定的值,不同于sleep(TIMEOUT)操作,WebDriverWait()会不断检测元素时候加载出来,如果元素已经加载出来,便直接执行,不再等待,可以提高速率。

3).爬取Following数据

爬取Following数据是整个工作中花费时间最多的地方,最开始直接使用Xpath定位方法,按照Following数量从1开始进行定位的方式,后面在实际试验中发现,由于Following界面是动态加载的,导致后面某一个Following的Xpath和前面的并不是按照序号连着的,而且也没有办法知道表现出什么规律,最后只能放弃。
接着想到,可以抓取Response的包,把需要的Following的数据直接抓下来离线解析,会省去很多操作。在选择什么方法进行抓包,也花了很多时间探索。首先使用的想到的mitmproxy,但是后来看了一下需要配置的东西有点多,学的东西也有点多,不想搞了就放弃了。接着是browsermobproxy,这个是比较简单适用的,但是由于twitter是需要FQ的,但是browsermobproxy只能设置一个代理,后面也就放弃了。后面看博客,了解到可以使用Chrome开发工具得到network的log日志文件,得到request-response对应的requestId,然后根据requestId得到对应的response的包,直接解析我们需要的数据,完美!
由于Chrome的日志访问一次之后,就会将起那么的清零,所有我们首先使用selenium模拟滚轮滑动到界面最下面,然后再一次性把所有的数据都抓取到。
a.模拟页面滚动
在这里插入图片描述

注:由于页面加载需要一些时间,中间需要强制使用sleep(),这是程序中花费时间比较多的地方,也是我目前没有解决的地方。

b.获取所有的Following对应的response
在这里插入图片描述
c.解析response包
在这里插入图片描述

4).迭代爬取

我们按照广度搜索的方式,一层一层的爬取数据,可以设定总共的爬取数据的数量,循环进行。
然后对数据进行保存。
在这里插入图片描述

使用try{}except{}结构,如果程序遇到不可控的错误,可以想把之前已经爬到的数据保存下面,然后最后的user开始重新进行爬取数据。

5.完整代码

import json
import pandas as pd
import os
from selenium import webdriver
from selenium.webdriver.common.by import By
from time import sleep
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities

TIMEOUT = 40   ### 测试的时候,各个地方的timeout可以设置的小一点,在实际程序运行的时候需要设置的更大一点
ALLPARENTSNUMBER = 10000
USERINFOLIST = []
TRUSTLIST = []
baseUrl = 'https://www.twitter.com'

def init(url):
    cookieFile = './cookies/twitterCookie.json'
    with open(cookieFile, 'r') as f:
        cookie = json.load(f)
    chromeOptions = webdriver.ChromeOptions()

    chromeOptions.add_argument("--proxy-server=http://127.0.0.1:7890")
    chromeOptions.add_argument('headless')

    capabilities = DesiredCapabilities.CHROME
    capabilities['goog:loggingPrefs'] = {"performance": "ALL"}  # newer: goog:loggingPrefs
    # 还有一种错误的做法
    # chrome_options.add_experimental_option('w3c', False)

    driver = webdriver.Chrome(options=chromeOptions, desired_capabilities=capabilities)
    while 1:
        try:
            driver.get(url)
            break
        except:
            sleep(TIMEOUT // 10)
    for item in cookie:
        driver.add_cookie(item)
    driver.get(url)
    print('Initialize Success!')
    return driver

def getUserInfo(driver,userPageUrl):
    driver.get(userPageUrl)
    nameXpath = '//*[@id="react-root"]/div/div/div[2]/main/div/div/div/div[1]/div/div[2]/div/div/div[1]/div/div[2]/div/div/div[1]/div/span[1]/span'
    uidXpath = '//*[@id="react-root"]/div/div/div[2]/main/div/div/div/div/div/div[2]/div/div/div[1]/div/div[2]/div/div/div[2]/div/span'
    introXpath = '//*[@id="react-root"]/div/div/div[2]/main/div/div/div/div/div/div[2]/div/div/div[1]/div/div[3]/div/div[1]/span'
    followingNumXpath = '//*[@id="react-root"]/div/div/div[2]/main/div/div/div/div/div/div[2]/div/div/div[1]/div/div[5]/div[1]/a/span[1]/span'

    name = WebDriverWait(driver, TIMEOUT).until(EC.presence_of_element_located((By.XPATH, nameXpath))).text
    uid = driver.find_element(By.XPATH,uidXpath).text
    try: intro = driver.find_element(By.XPATH,introXpath).text
    except: intro = ''
    try: followingNum = int((driver.find_element(By.XPATH,followingNumXpath).text).replace(',','').replace('.','').replace('K','000'))
    except: followingNum = 1000
    return [name,uid,intro,followingNum]


def getFollowingInfo(driver,userInfo):
    userFollowingPageUrl = baseUrl+'/'+userInfo[1]+'/following'
    driver.get(userFollowingPageUrl)
    sleep(TIMEOUT//10)
    scrollUntilLoaded(driver)
    targetUserName = userInfo[0]
    sleep(TIMEOUT//10)
    trueFollowerNum = getFollowingResponse(targetUserName,driver)
    return trueFollowerNum


def getFollowingResponse(targetUserName,driver):
    tmpTrueFollowerNum = 0
    for row in driver.get_log('performance'):
        log_data = row
        log_json = json.loads(log_data['message'])
        log = log_json['message']

        if log['method'] == 'Network.responseReceived' and 'Following' in log['params']['response']['url']:
            requestId = log['params']['requestId']
            try:
                responseBody = driver.execute_cdp_cmd("Network.getResponseBody", {"requestId": requestId})['body']
                oneResponseNum = decodeFollowingReponse(targetUserName,responseBody)
                tmpTrueFollowerNum += oneResponseNum
            except:
                pass
    print('\nfollowingNumbers:\t',tmpTrueFollowerNum)
    return tmpTrueFollowerNum

def decodeFollowingReponse(targetUserName,responseBody):
    responseBody = json.loads(responseBody)
    allInstructions = responseBody['data']['user']['result']['timeline']['timeline']['instructions']
    for instruction in  allInstructions:
        if instruction['type']=='TimelineAddEntries':
            allEntries = instruction['entries']
            break
    verifiedEntries = 0
    for ids in range(len(allEntries)-2):
        result = allEntries[ids]['content']['itemContent']['user_results']['result']
        if result.get('legacy'):
            userContent = result['legacy']
            name = userContent['name']
            intro = userContent['description']
            uid = userContent['screen_name']
            isVerified = userContent['verified']
            if isVerified:
                verifiedEntries +=1
                TRUSTLIST.append([targetUserName,name])
                USERINFOLIST.append([name,uid,intro,0])
    return verifiedEntries


def scrollUntilLoaded(driver):
    last_height = driver.execute_script("return document.body.scrollHeight")

    while True:
        driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
        sleep(TIMEOUT//6)
        new_height = driver.execute_script("return document.body.scrollHeight")
        if new_height == last_height:
            break
        last_height = new_height


def save():
    userInfoFrame = pd.DataFrame(USERINFOLIST,columns=['name','uid','intro','followingNum'])
    trustFrame = pd.DataFrame(TRUSTLIST,columns=['followee','follower'])

    fileDir = "Twitter_"+str(ALLPARENTSNUMBER)+'/'
    if not os.path.exists(fileDir):
        os.makedirs(fileDir)
    userInfoFrame.to_csv(fileDir+"userInfo.csv",sep='\t')
    trustFrame.to_csv(fileDir+"trusts.csv",sep='\t')

if __name__ == '__main__':
    driver = init(baseUrl)
    startUserUrl = baseUrl+'/POTUS45'
    # 对第一个用户的处理
    startUserInfo = getUserInfo(driver, startUserUrl)
    USERINFOLIST.append(startUserInfo)
    startUserFollowerNum = getFollowingInfo(driver,startUserInfo)
    USERINFOLIST[0][-1] = startUserFollowerNum

    # 后续用户重复处理
    levelCount = 0
    parentsUserInfo = USERINFOLIST[0]
    parentsUserNum, allUserNum = 1,1
    try:
        while True:
            nextUserInfo = USERINFOLIST[parentsUserNum]
            trueFollowerNum = getFollowingInfo(driver,nextUserInfo)
            USERINFOLIST[allUserNum][-1] = trueFollowerNum
            allUserNum += trueFollowerNum
            parentsUserNum += 1
            print('number\t:',parentsUserNum)
            if parentsUserNum==ALLPARENTSNUMBER:
                break
        savedUserInfoLen = len(USERINFOLIST)
        for restUser in range(parentsUserNum+1,savedUserInfoLen):
            userUrl = baseUrl+'/'+USERINFOLIST[restUser][1]
            trueFollowerNum = getUserInfo(driver,userUrl)
            USERINFOLIST[restUser][-1] = trueFollowerNum
            print('number\t:',restUser)
    except:
        save()