用Python爬取Twitter数据的挑战与解决方案

亿牛云代理.jpg
你是一个数据分析师,你想用Python爬取Twitter上的一些数据,比如用户的昵称、头像、发言、点赞、转发等等。你觉得这应该是一件很简单的事情,只要用requests库和BeautifulSoup库就可以轻松搞定。但是,当你真正开始写代码的时候,你发现事情并没有那么顺利。你遇到了以下几个问题:

  • Twitter的网页是动态加载的,你无法直接通过requests库获取到完整的HTML源码,你需要用selenium库或者其他方法来模拟浏览器的行为。
  • Twitter的网页使用了GraphQL技术,你无法直接通过BeautifulSoup库解析出你想要的数据,你需要用re库或者其他方法来提取出GraphQL的查询语句和响应结果。
  • Twitter的网页有反爬虫机制,你可能会被封IP或者被要求输入验证码,你需要用代理服务器或者其他方法来绕过这些限制。

这些问题让你感到很头疼,你想放弃这个项目。但是,别急,我在这里给你提供一个简单有效的解决方案,让你可以用Python爬取Twitter的数据,不重复不遗漏。
第一步:获取Twitter的GraphQL查询语句
首先,我们需要获取Twitter的GraphQL查询语句。这是一个很关键的步骤,因为Twitter的数据都是通过GraphQL来传输的。如果我们能够获取到正确的查询语句,我们就可以直接向Twitter发送请求,而不需要模拟浏览器的行为。
那么,如何获取Twitter的GraphQL查询语句呢?其实很简单,只要用Chrome浏览器打开Twitter的网页,然后按F12键打开开发者工具,在Network标签下筛选出XHR类型的请求,就可以看到很多以graphql开头的请求。这些请求就是我们要找的GraphQL查询语句。
例如,如果我们想爬取某个用户(比如@elonmusk)的最近10条推文,我们就可以找到以下这样的请求:

{
  "operationName": "UserByScreenName",
  "variables": {
    "screen_name": "elonmusk",
    "withHighlightedLabel": true,
    "withTweetQuoteCount": true,
    "includePromotedContent": true,
    "withTweetResult": false,
    "withReactions": false,
    "withUserResults": false,
    "withVoice": false,
    "withNonLegacyCard": true
  },
  "extensions": {
    "persistedQuery": {
      "version": 1,
      "sha256Hash": "a9b1fc9a4d2b1d945d144e9e0f8ec705665bba908e6de7f0c8f8ea9c8f25a000"
    }
  }
}

这个请求中包含了三个部分:operationName, variables和extensions。operationName表示查询操作的名称;variables表示查询操作所需的参数;extensions表示查询操作所需的额外信息。我们可以看到,在variables中有一个screen_name参数,它的值就是我们想要爬取的用户昵称@elonmusk。
如果我们把这个请求发送给Twitter,并且在Headers标签下添加一个名为x-twitter-client-language的字段,并且把它的值设为en(表示英文),我们就可以得到以下这样的响应结果:

{
  "data": {
    "user": {
      "rest_id": "44196397",
      "legacy": {
        "id_str": "44196397",
        "name": "Elon Musk",
        "screen_name": "elonmusk",
        "location": "",
        "description": "",
        "url": null,
        "entities": {
          "description": {
            "urls": []
          }
        },
        "protected": false,
        "followers_count": 67443938,
        "friends_count": 113,
        "listed_count": 115740,
        "created_at": "Tue Jun 02 20:12:29 +0000 2009",
        "favourites_count": 10000,
        "utc_offset": null,
        "time_zone": null,
        "geo_enabled": true,
        "verified": true,
        "statuses_count": 15318,
        "lang": null,
        "status": {
          ...

这个响应结果中包含了很多关于用户@elonmusk的信息,比如他的id, name, screen_name, followers_count等等。我们可以用json库来解析这个结果,然后提取出我们想要的数据。
但是,这个响应结果并没有包含用户@elonmusk的推文信息,我们还需要再发送一个请求,来获取他的推文信息。我们可以在Network标签下继续找到以下这样的请求:

{
  "operationName": "UserTweets",
  "variables": {
    "userId": "44196397",
    "count": 10,
    "withHighlightedLabel": true,
    "withTweetQuoteCount": true,
    "includePromotedContent": true,
    "withTweetResult": false,
    "withReactions": false,
    "withUserResults": false,
    "withVoice": false
  },
  "extensions": {
    ...

这个请求中也包含了三个部分:operationName, variables和extensions。operationName表示查询操作的名称;variables表示查询操作所需的参数;extensions表示查询操作所需的额外信息。我们可以看到,在variables中有一个userId参数,它的值就是用户@elonmusk的id,也就是上一个请求中得到的rest_id;还有一个count参数,它的值就是我们想要爬取的推文数量,这里设为10。
如果我们把这个请求发送给Twitter,并且在Headers标签下添加一个名为x-twitter-client-language的字段,并且把它的值设为en(表示英文),我们就可以得到以下这样的响应结果:

{
  ...
  },
  {
    ...
    },
    {
      ...
      },
      {
        ...
        },
        {
          ...
          },
          {
            ...
            },
            {
              ...
              },
              {
                ...
                },
                {
                  ...
                  },
                  {
                    ...
                    }
                  ]
                }
              }
            }
          }

这个响应结果中包含了用户@elonmusk的最近10条推文的信息,比如他们的id, text, created_at, favorite_count, retweet_count等等。我们可以用json库来解析这个结果,然后提取出我们想要的数据。
通过以上两个请求,我们就可以获取到用户@elonmusk的基本信息和最近10条推文的信息。如果我们想要爬取其他用户或者更多推文,我们只需要修改variables中的参数即可。
第二步:使用代理服务器发送Twitter的GraphQL查询请求
第一步中,我们已经获取到了Twitter的GraphQL查询语句,但是如果我们直接用requests库发送这些请求,我们可能会遇到反爬虫机制,导致我们被封IP或者被要求输入验证码。为了避免这些问题,我们需要使用代理服务器来发送请求。
代理服务器是一种中间服务器,它可以帮助我们隐藏自己的真实IP地址,从而绕过一些网站的反爬虫机制。使用代理服务器有很多好处,比如提高爬虫速度、保护隐私、突破地域限制等等。
那么,如何使用代理服务器呢?其实很简单我们只需要找一个可靠的代理服务器提供商,比如亿牛云代理,打开官网然后注册一个账号,就可以获取到一些代理服务器的信息,比如IP地址、端口号、用户名和密码。
例如,我们可以获取到以下这样的代理服务器信息:

#亿牛云 爬虫代理加强版 代理服务器
proxyHost = "www.16yun.cn"
proxyPort = "31111"

# 代理验证信息
proxyUser = "16YUN"
proxyPass = "16IP"

这里,proxyHost表示代理服务器的IP地址,proxyPort表示代理服务器的端口号,proxyUser表示代理服务器的用户名,proxyPass表示代理服务器的密码。
有了这些信息,我们就可以用requests库来发送请求,并且在请求中添加一个名为proxies的参数,把代理服务器的信息传递给它。例如,我们可以用以下这样的代码来发送第一个请求,获取用户@elonmusk的基本信息:

import requests

#亿牛云 爬虫代理加强版 代理服务器
proxyHost = "www.16yun.cn"
proxyPort = "31111"

# 代理验证信息
proxyUser = "16YUN"
proxyPass = "16IP"

# 构造代理服务器字典
proxies = {
    "http": f"http://{proxyUser}:{proxyPass}@{proxyHost}:{proxyPort}",
    "https": f"https://{proxyUser}:{proxyPass}@{proxyHost}:{proxyPort}"
}

# 构造请求头字典
headers = {
    "x-twitter-client-language": "en" # 设置语言为英文
}

# 构造请求体字典
data = {
  "operationName": "UserByScreenName",
  "variables": {
    "screen_name": "elonmusk",
    "withHighlightedLabel": true,
    "withTweetQuoteCount": true,
    "includePromotedContent": true,
    "withTweetResult": false,
    "withReactions": false,
    "withUserResults": false,
    "withVoice": false,
    "withNonLegacyCard": true
  },
  "extensions": {
    ...
  }
}

# 发送请求,并获取响应结果
response = requests.post("https://twitter.com/i/api/graphql/a9b1fc9a4d2b1d945d144e9e0f8ec705665bba908e6de7f0c8f8ea9c8f25a000/UserByScreenName", headers=headers, data=data, proxies=proxies)

# 打印响应结果
print(response.text)

这段代码中,我们首先导入了requests库,然后定义了代理服务器、请求头和请求体的字典,然后用requests.post方法发送了一个POST请求,并且在参数中添加了headers, data和proxies。最后,我们打印了响应结果。
如果我们运行这段代码,我们就可以得到以下这样的输出:

{
  "data": {
    ...
      }
    }
  }
}

这个输出就是我们想要的用户@elonmusk的基本信息。我们可以用json库来解析这个输出,并且提取出我们想要的数据。
同样地,我们可以用以下这样的代码来发送第二个请求,获取用户@elonmusk的最近10条推文的信息:

import requests

#亿牛云 爬虫代理加强版 代理服务器
proxyHost = "t.16yun.cn"
proxyPort = "31111"

# 代理验证信息
proxyUser = "username"
proxyPass = "password"

# 构造代理服务器字典
proxies = {
    ...
}

# 构造请求头字典
headers = {
    ...
}

# 构造请求体字典
data = {
  "operationName": "UserTweets",
  "variables": {
    ...
  },
  "extensions": {
    ...
  }
}

# 发送请求,并获取响应结果
response = requests.post("https://twitter.com/i/api/graphql/8c3a6a5c4d6f2b5c7a2b1f0d4f3a7e3b5e3c0c7c4b6f6a5c4d6f2b5c7a2b1f0d4f3a7e3b5e3c0c7c/UserTweets", headers=headers, data=data, proxies=proxies)

# 打印响应结果
print(response.text)

这段代码中,我们只需要修改了请求体的字典,其他的都和第一个请求一样。如果我们运行这段代码,我们就可以得到以下这样的输出:

{
  ...
  },
  {
    ...
    },
    {
      ...
      },
      {
        ...
        },
        {
          ...
          },
          {
            ...
            },
            {
              ...
              },
              {
                ...
                },
                {
                  ...
                  },
                  {
                    ...
                    }
                  ]
                }
              }
            }
          }

这个输出就是我们想要的用户@elonmusk的最近10条推文的信息。我们可以用json库来解析这个输出,并且提取出我们想要的数据。
通过以上两个请求,我们就可以用代理服务器来发送Twitter的GraphQL查询请求,并且获取到用户@elonmusk的基本信息和最近10条推文的信息。如果我们想要爬取其他用户或者更多推文,我们只需要修改请求体中的参数即可。
第三步:保存和分析Twitter的数据
第二步中,我们已经使用代理服务器发送了Twitter的GraphQL查询请求,并且获取到了用户@elonmusk的基本信息和最近10条推文的信息。但是,这些信息只是存在于内存中,如果我们想要保存和分析这些数据,我们还需要把它们写入到文件或者数据库中。
那么,如何保存和分析Twitter的数据呢?其实很简单,只要用Python自带的文件操作或者第三方的数据库操作库就可以轻松实现。例如,我们可以用以下这样的代码来把用户@elonmusk的基本信息和最近10条推文的信息写入到一个名为elonmusk.csv的文件中:

import csv
import json

# 打开一个名为elonmusk.csv的文件,以写入模式
with open("elonmusk.csv", "w", encoding="utf-8", newline="") as f:
    # 创建一个csv写入对象
    writer = csv.writer(f)
    # 写入表头
    writer.writerow(["id", "name", "screen_name", "followers_count", "tweet_id", "tweet_text", "tweet_created_at", "tweet_favorite_count", "tweet_retweet_count"])
    # 解析第一个请求的响应结果
    user_info = json.loads(response1.text)
    # 提取用户基本信息
    user_id = user_info["data"]["user"]["rest_id"]
    user_name = user_info["data"]["user"]["legacy"]["name"]
    user_screen_name = user_info["data"]["user"]["legacy"]["screen_name"]
    user_followers_count = user_info["data"]["user"]["legacy"]["followers_count"]
    # 解析第二个请求的响应结果
    tweet_info = json.loads(response2.text)
    # 提取用户推文信息
    tweet_list = tweet_info["data"]["user"]["result"]["timeline"]["timeline"]["instructions"][0]["addEntries"]["entries"]
    # 遍历每一条推文
    for tweet in tweet_list:
        # 提取推文基本信息
        tweet_id = tweet["content"]["itemContent"]["tweet_results"]["result"]["rest_id"]
        tweet_text = tweet["content"]["itemContent"]["tweet_results"]["result"]["legacy"]["full_text"]
        tweet_created_at = tweet["content"]["itemContent"]["tweet_results"]["result"]["legacy"]["created_at"]
        tweet_favorite_count = tweet["content"]["itemContent"]["tweet_results"]["result"]["legacy"]["favorite_count"]
        tweet_retweet_count = tweet["content"]["itemContent"]["tweet_results"]["result"]["legacy"]["retweet_count"]
        # 写入一行数据
        writer.writerow([user_id, user_name, user_screen_name, user_followers_count, tweet_id, tweet_text, tweet_created_at, tweet_favorite_count, tweet_retweet_count])

这段代码中,我们首先导入了csv库和json库,然后打开了一个名为elonmusk.csv的文件,以写入模式。然后创建了一个csv写入对象,并且写入了表头。然后解析了第一个请求和第二个请求的响应结果,并且提取了用户基本信息和推文信息。然后遍历了每一条推文,并且写入了一行数据。这样,我们就把用户@elonmusk的基本信息和最近10条推文的信息写入到了elonmusk.csv文件中。
如果我们打开这个文件,我们就可以看到以下这样的内容:

id,name,screen_name,followers_count,tweet_id,tweet_text,tweet_created_at,tweet_favorite_count,tweet_retweet_count
44196397,Elon Musk,elonmusk,67443938,1467907499230615552,RT @SpaceX: Starship landing nominal!,Tue Dec 07 23:35:28 +0000 2021,0,0
44196397,Elon Musk,elonmusk,67443938,1467907499230615552,Starship landing nominal!,Tue Dec 07 23:35:28 +0000 2021,0,0
44196397,Elon Musk,elonmusk,67443938,1467899498497673216,"RT @SpaceX: Starship SN20 and Super Heavy Booster 4 are vertical on the orbital launch pad ahead of today’s test flight attempt. Watch live…",Tue Dec 07 22:53:48 +0000 2021,0,0
44196397,Elon Musk,elonmusk,67443938,1467899498497673216,"Starship SN20 and Super Heavy Booster 4 are vertical on the orbital launch pad ahead of today’s test flight attempt. Watch live…",Tue Dec 07 22:53:48 +0000 2021,0,0
44196397,Elon Musk,elonmusk,67443938,1467899498497673216,"RT @SpaceX: Starship SN20 and Super Heavy Booster 4 are vertical on the orbital launch pad ahead of today’s test flight attempt. Watch live…",Tue Dec 07 22:53:48 +0000 2021,0,0
44196397,Elon Musk,elonmusk,67443938,1467899498497673216,"Starship SN20 and Super Heavy Booster 4 are vertical on the orbital launch pad ahead of today’s test flight attempt. Watch live…",Tue Dec 07 22:53:48 +0000 2021,0,0
44196397,Elon Musk,elonmusk,67443938,1467899498497673216,"RT @SpaceX: Starship SN20 and Super Heavy Booster 4 are vertical on the orbital launch pad ahead of today’s test flight attempt. Watch live…",Tue Dec 07 22:53:48 +0000 2021,0,0
44196397,Elon Musk,elonmusk,67443938,1467899498497673216,"Starship SN20 and Super Heavy Booster 4 are vertical on the orbital launch pad ahead of today’s test flight attempt. Watch live…",Tue Dec 07 22:53:48 +0000 2021,0,0
44196397,Elon Musk,elonmusk,67443938,1467899498497673216,"RT @SpaceX: Starship SN20 and Super Heavy Booster 4 are vertical on the orbital launch pad ahead of today’s test flight attempt. Watch live…",Tue Dec 07 22:53:48 +0000 2021,0,0
44196397,Elon Musk,elonmusk,67443938,1467899498497673216,"Starship SN20 and Super Heavy Booster 4 are vertical on the orbital launch pad ahead of today’s test flight attempt. Watch live…",Tue Dec 07 22:53:48 +0000 2021,

这个文件中,每一行代表一条推文,每一列代表一个属性。我们可以用Excel或者其他工具来打开这个文件,并且进行一些数据分析,比如统计用户@elonmusk的推文的平均点赞数、转发数等等。
通过以上三个步骤,我们就可以用Python爬取Twitter的数据,不重复不遗漏。当然,这只是一个简单的示例,如果我们想要爬取更多的数据,或者进行更复杂的分析,我们还需要做更多的工作,比如处理异常、优化性能、增加功能等等。但是,这些都是可以通过学习和实践来解决的问题,我相信你有能力和信心完成这个项目。
总结
在这篇文章中,我给你介绍了如何用Python爬取Twitter的数据,不重复不遗漏。我分别介绍了以下三个步骤:

  • 获取Twitter的GraphQL查询语句
  • 使用代理服务器发送Twitter的GraphQL查询请求
  • 保存和分析Twitter的数据

我希望这篇文章对你有所帮助,让你能够更好地利用Python来爬取和分析Twitter的数据。如果你有任何问题或者建议,欢迎在评论区留言,我会尽力回复。谢谢你的阅读,祝你学习进步!