in

如何使用Charles代理进行网页爬取

如何使用Charles代理进行网页爬取

Charles代理是一个HTTP调试代理,可以检查网络调用和调试SSL流量。使用Charles,你能够检查请求/响应、头信息和cookies。今天我们将看到如何设置Charles,以及如何使用查尔斯代理进行网页爬取。我们将专注于从Javascript重的网页和移动应用程序中提取数据。Charles位于你的应用程序和互联网之间。

Charles就像类固醇的Chrome开发工具。它有许多令人难以置信的功能。

  • 带宽节流以模拟缓慢的互联网连接
  • 请求编辑(例如,测试后端API的行为)。
  • 重复的请求
  • 对请求列表进行全文搜索(对网络抓取非常有趣)。
  • 更多

在调试单页面应用程序或移动应用程序时,Charles是一个非常有趣的工具。


安装和设置

我们将看到如何在macOS上安装和配置Charles,但也有一个Windows和Linux版本。

  • 首先访问https://www.charlesproxy.com/download/,下载Charles。
  • 安装Charles后,你需要安装其根证书。这将使查尔斯能够拦截和解密SSL流量。
  • 点击安装根证书后,它将打开你的macOS钥匙串访问,你将不得不打开信任菜单并点击 “总是信任”。
  • 这将要求你提供你的系统密码。
  • 现在你需要进入代理 > SSL代理设置,并添加 “*”或你想检查SSL的域名。

Charles现在将捕获你所选择的域名上的HTTPS流量。


在单页面应用中寻找隐藏的API

在传统的服务器端渲染的网站中,HTML代码由后台服务构建,完整的页面被返回到HTTP客户端(一般是你的浏览器)。 在过去的10年中,越来越多的网站使用单页应用程序框架(如React.js、Vue.js或Angular)进行客户端渲染。

这些框架正在向后端API发送许多请求,直接使用这些API而不是爬取网站,用无头浏览器渲染Javascript来提取你想要的数据可能是一个好主意。这将会快得多,你不需要昂贵的硬件(无头浏览器需要大量的内存和强大的CPU)。

我们将看看不同的单页应用程序,看看Charles代理如何帮助你从后端API发现和提取数据。

让我们从ProductHunt开始

ProductHunt是一个著名的在线发布产品的网站。它在科技生态系统中非常受欢迎。每天都有几十个项目推出,所以首页只加载当天的产品。有一个无限的滚动来查看前几天的产品。

我们将使用Charles代理来分析后端API调用,并用一些Python代码来重现它。

现在打开Charles代理,进入Producthunt主页,滚动几次到页面底部。

默认情况下,Charles会捕捉你的系统发出的每个HTTP请求,而不仅仅是你的浏览器。因此,你会得到大量的 “污染”。你可以通过输入你感兴趣的域来过滤这些。

如果你点击其中一个到/frontend/graphql端点的POST请求,你可以检查请求和响应。

这些请求有很多事情要做,但如果你比较一下被发送到GraphQL API的内容,似乎唯一改变的是cursor参数。

它是base64编码的,但幸运的是,Charles有一个功能可以快速解码Base64内容。你只需选择它并右键点击。

Charles提供的一个杀手锏是能够编辑任何请求并重播。在我们的案例中,这真的很好,因为在请求里面有很多头信息/cookies值,所以用HTTP客户端或在你的代码里面试图重现这个请求将是一场噩梦。

为了做到这一点,你可以在请求上点击右键,然后点击Compose。

然后你可以玩玩cursor值,用base64编码的不同页码来替换它。然后点击 “执行”。

你也可以尝试删除不同的cookies(会有效果)。这是个好消息,因为如果使用这个端点必须要有cookie,那么用我们的Python代码重现这个请求就会更加复杂。

现在你可以把请求导出到cURL。

然后你可以使用像这样的工具,将cURL命令转换为Python代码(使用奇妙的Requests包):https://curl.trillworks.com/

我只是添加了verify=False参数,以避免Request的SSL警告:

import requests


headers = {
    'Host': 'www.producthunt.com',
    'accept': '*/*',
    'x-requested-with': 'XMLHttpRequest',
    'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36',
    'content-type': 'application/json',
    'origin': 'https://www.producthunt.com',
    'sec-fetch-site': 'same-origin',
    'sec-fetch-mode': 'cors',
    'sec-fetch-dest': 'empty',
    'referer': 'https://www.producthunt.com/',
    'accept-language': 'en-US,en;q=0.9',
}

data = '{"operationName":"HomePage","variables":{"cursor":"OA==","featured":true,"includePromotedPost":false,"visibleOnHomepage":true,"includeLayout":false},"query":"query HomePage($cursor: String, $postCursor: String, $featured: Boolean!, $includePromotedPost: Boolean!, $visibleOnHomepage: Boolean!) {\\n sections(first: 1, after: $cursor, featured: $featured) {\\n edges {\\n cursor\\n node {\\n id\\n date\\n cutoff_index\\n posts_count\\n cards(first: 1, after: $cursor) {\\n edges {\\n node {\\n ...FeedCards\\n __typename\\n }\\n __typename\\n }\\n __typename\\n }\\n posts(after: $postCursor, visible_on_homepage: $visibleOnHomepage) {\\n edges {\\n node {\\n ...PostItemList\\n featured_comment {\\n id\\n body: body_text\\n user {\\n id\\n ...UserImageLink\\n __typename\\n }\\n __typename\\n }\\n __typename\\n }\\n __typename\\n }\\n pageInfo {\\n endCursor\\n hasNextPage\\n __typename\\n }\\n __typename\\n }\\n __typename\\n }\\n __typename\\n }\\n pageInfo {\\n endCursor\\n hasNextPage\\n __typename\\n }\\n __typename\\n }\\n ad(kind: \\"feed\\") @include(if: $includePromotedPost) {\\n ...AdFragment\\n __typename\\n }\\n promoted_email_campaign(promoted_type: HOMEPAGE) @include(if: $includePromotedPost) {\\n id\\n abTestName\\n abVariant {\\n id\\n ...PromotedEmailAbTestVariantFragment\\n __typename\\n }\\n ...PromotedEmailCampaignFragment\\n __typename\\n }\\n daily_newsletter {\\n id\\n subject\\n __typename\\n }\\n viewer {\\n id\\n email\\n has_newsletter_subscription\\n __typename\\n }\\n ph_homepage_og_image_url\\n}\\n\\nfragment FeedCards on Card {\\n ...NewPostsCard\\n ...BestProductsFromLastWeekCard\\n ...MakersDiscussionCardFragment\\n ...GoldenKittyCardFragment\\n __typename\\n}\\n\\nfragment NewPostsCard on NewPostsCard {\\n is_dismissed\\n kind\\n posts {\\n ...PostItemList\\n __typename\\n }\\n __typename\\n}\\n\\nfragment PostItemList on Post {\\n id\\n ...PostItem\\n __typename\\n}\\n\\nfragment PostItem on Post {\\n id\\n _id\\n comments_count\\n name\\n shortened_url\\n slug\\n tagline\\n updated_at\\n topics {\\n edges {\\n node {\\n id\\n name\\n slug\\n __typename\\n }\\n __typename\\n }\\n __typename\\n }\\n ...PostThumbnail\\n ...PostVoteButton\\n __typename\\n}\\n\\nfragment PostThumbnail on Post {\\n id\\n name\\n thumbnail {\\n id\\n media_type\\n ...MediaThumbnail\\n __typename\\n }\\n ...PostStatusIcons\\n __typename\\n}\\n\\nfragment MediaThumbnail on Media {\\n id\\n image_uuid\\n __typename\\n}\\n\\nfragment PostStatusIcons on Post {\\n name\\n product_state\\n __typename\\n}\\n\\nfragment PostVoteButton on Post {\\n _id\\n id\\n featured_at\\n updated_at\\n created_at\\n disabled_when_scheduled\\n has_voted\\n ... on Votable {\\n id\\n votes_count\\n __typename\\n }\\n __typename\\n}\\n\\nfragment BestProductsFromLastWeekCard on BestProductsFromLastWeekCard {\\n posts {\\n ...PostItemList\\n __typename\\n }\\n __typename\\n}\\n\\nfragment MakersDiscussionCardFragment on MakersDiscussionCard {\\n isDismissed\\n discussion {\\n _id\\n id\\n ...DiscussionThreadListItem\\n __typename\\n }\\n __typename\\n}\\n\\nfragment DiscussionThreadListItem on DiscussionThread {\\n _id\\n id\\n title\\n description\\n descriptionHtml\\n slug\\n commentsCount\\n can_comment: canComment\\n discussionPath\\n canEdit\\n votesCount\\n hasVoted\\n createdAt\\n poll {\\n ...PollFragment\\n __typename\\n }\\n user {\\n id\\n name\\n username\\n headline\\n avatar\\n __typename\\n }\\n __typename\\n}\\n\\nfragment PollFragment on Poll {\\n id\\n answersCount\\n hasAnswered\\n options {\\n id\\n text\\n imageUuid\\n answersCount\\n answersPercent\\n hasAnswered\\n __typename\\n }\\n __typename\\n}\\n\\nfragment GoldenKittyCardFragment on GoldenKittyCard {\\n is_dismissed\\n category_for_voting {\\n id\\n slug\\n __typename\\n }\\n __typename\\n}\\n\\nfragment PromotedEmailCampaignFragment on PromotedEmailCampaign {\\n id\\n _id\\n title\\n tagline\\n thumbnail\\n ctaText\\n __typename\\n}\\n\\nfragment PromotedEmailAbTestVariantFragment on PromotedEmailAbTestVariant {\\n id\\n _id\\n title\\n tagline\\n thumbnail\\n ctaText\\n __typename\\n}\\n\\nfragment AdFragment on LegacyAdsUnion {\\n ... on PromotedPost {\\n id\\n ...LegacyPromotedPostItem\\n __typename\\n }\\n ... on AdChannel {\\n id\\n post {\\n id\\n slug\\n name\\n updated_at\\n comments_count\\n ...PostVoteButton\\n __typename\\n }\\n ctaText\\n dealText\\n adName: name\\n adTagline: tagline\\n adThumbnailUuid: thumbnailUuid\\n adUrl: url\\n __typename\\n }\\n __typename\\n}\\n\\nfragment LegacyPromotedPostItem on PromotedPost {\\n id\\n deal\\n post {\\n id\\n ...PostItem\\n __typename\\n }\\n name\\n tagline\\n ctaText\\n url\\n thumbnailUuid\\n ...ViewableImpressionSubject\\n __typename\\n}\\n\\nfragment ViewableImpressionSubject on Node {\\n id\\n __typename\\n}\\n\\nfragment UserImageLink on User {\\n id\\n _id\\n name\\n username\\n avatar\\n ...UserImage\\n __typename\\n}\\n\\nfragment UserImage on User {\\n id\\n post_upvote_streak\\n name\\n avatar\\n __typename\\n}\\n"}'

response = requests.post('https://www.producthunt.com/frontend/graphql', headers=headers, cookies=cookies, data=data, verify=False)

然后就可以了!你现在可以通过调整光标参数来获得你想要的页码,你只需要把它编码成base64。


移动应用逆向工程

在网络应用中检查/修改网络调用的能力是非常酷的。但你知道什么更酷吗?是的,移动应用程序!这是很重要的。当然,它可能在服务条款中被禁止,所以在对移动应用程序进行任何请求分析之前,请确保阅读它。

几年前,让Charles代理在你的移动设备上工作有点复杂,你必须使用你的桌面Charles作为代理,在同一个wifi网络上等等。自2018年以来,他们推出了一个原生的iOS应用程序,你可以在应用程序商店下载:https://apps.apple.com/us/app/charles-proxy/id1134218562

现在,让Charles拦截你最喜欢的应用程序的HTTPS流量更容易了。安装该应用程序,然后在主屏幕上激活它。

一旦你激活了Charles并设置了VPN配置,你需要进入设置屏幕并启用SSL代理(只需按照说明操作)。

当你打开SSL代理时,一些应用程序可能会停止工作。当你安装Charles时,你也在安装一个根证书,以便Charles可以检查你的SSL流量。

这有点像 “中间人 “攻击(但在这种情况下你是在攻击你自己)。

一些应用程序正在验证根证书,而不会接受Charles的根证书。这就是所谓的SSL钉牢。

我们要分析的iOS应用程序是https://dev.to :https://apps.apple.com/us/app/dev-community/id1439094790

Dev.to是一个伟大的开发者社区网站,人们可以在这里发表文章。这个应用程序非常直接,它允许在dev.to上以原生体验的方式进行阅读。

现在打开激活Charles的应用程序,并滚动几次以加载更多的文章。

与Charles桌面应用程序一样,你将需要过滤dev.to域,以避免看到其他所有应用程序发送的请求。

然后你可以点击请求序列,并分享到你的Mac上作进一步分析(不需要在你的移动设备上分析请求)。

现在我们要在Charles桌面程序上打开请求会话,玩玩请求。双击你的下载文件夹中的Charles会话文件(以.chlsj结尾)。

像上一部分一样,你可以用这个请求会话做完全相同的分析。

有趣的请求是/search端点上的请求。这是一个标准的REST API。

似乎没有任何认证/cookies或特定的头文件需要获得成功的响应。你可以通过以下方式验证这一点。

  • 用CMD+N创建一个新的会话
  • 在dev.to上进行过滤,就像在前一部分一样
  • 返回到移动开发会话
  • 单击 “Compose“按钮并删除cookies和头文件,然后Execute
  • 返回到桌面会话

有趣的参数是pageper_page计数。你可以用这些值来玩。

在将请求导出到cURL,然后将其转换为Python。

import requests


params = (
    ('per_page', '5'),
    ('page', '1'),
    ('sort_by', 'hotness_score'),
    ('sort_direction', 'desc'),
    ('approved', ''),
    ('class_name', 'Article'),
)

response = requests.get('https://dev.to/search/feed_content', verify=False, params=params)
print(response.text)

[文中代码源自Scrapingbee]

这是我最喜欢的技术之一,可以从难以抓取的网站中提取数据。它比使用无头浏览器快得多,而且解析JSON响应比经常变化的混乱的HTML代码容易得多。


总    结

还有许多我们在本教程中没有看到的Charles功能。例如,断点和响应重写。这对于想要测试/调试单页应用程序或移动应用程序的人来说,可能非常有趣。

当你想从网站上提取数据或对移动应用程序进行逆向工程时,Charles非常方便,但它也几乎是对复杂网站进行任何形式的浏览器自动化的必经之路。

例如,用普通的浏览器开发工具,认证工作流程可能非常难以理解。一旦你开始有JWT令牌、CSRF令牌和Oauth,它就开始变得混乱,能够对一系列请求进行全文搜索并重放/修改请求是必须的。

如果你喜欢这篇文章,你可能想阅读这些。

我希望你喜欢这个关于Charles和一般逆向工程的介绍,并希望你能在你喜欢的应用程序上进行实验。

blank

Written by 爬取 大师

阿里P12级别选手,能够突破各种反爬, 全能的爬取大师,擅长百万级的数据抓取!没有不能爬,只有你不敢想,有爬取项目可以联系我邮箱 [email protected] (带需求和预算哈, 不然多半不回复)