热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

c#基于Titanium爬取微信公众号历史文章列表

这篇文章主要介绍了c#基于Titanium爬取微信公众号历史文章列表,帮助大家更好的理解和学习使用c#,感兴趣的朋友可以了解下

github:https://github.com/justcoding121/Titanium-Web-Proxy

什么是Titanium

基于C#的跨平台异步HTTP(S)代理服务器
类似的还有:

https://github.com/http-party/node-http-proxy

原理简述

对于HTTP

顾名思义,其实代理就是一个「中间人」角色,对于连接到它的客户端来说,它是服务端;对于要连接的服务端来说,它是客户端。它就负责在两端之间来回传送 HTTP 报文。

对于HTTPS

由于HTTPS加入了CA证书的校验,服务端可不验证客户端的证书,中间人可以伪装成客户端与服务端成功完成 TLS 握手;但是中间人没有服务端证书私钥,无论如何也无法伪装成服务端跟客户端建立 TLS 连接,所以我们需要换个方式代理HTTPS请求。

HTTPS在传输层之上建立了安全层,所有的HTTP请求都在安全层上传输。既然无法通过像代理一般HTTP请求的方式在应用层代理HTTPS请求,那么我们就退而求其次为在传输层为客户端和服务器建立起TCP连接。一旦 TCP 连接建好,代理无脑转发后续流量即可。所以这种代理,理论上适用于任意基于 TCP 的应用层协议,HTTPS 网站使用的 TLS 协议当然也可以。这也是这种代理为什么被称为隧道的原因。
但是这样子无脑转发我们就无法获取到他们交互的数据了,怎么办?
此时就需要代理变身为伪HTTPS服务器,然后让客户端信任我们自定义的根证书,从而在客户端和代理、代理和服务端之间都能成功建立 TLS 连接。对于代理来说,两端的TLS流量都是可以解密的。

最后如果这个代理我们可编程,那么我们就可以对传送的HTTP报文做控制。
相关的应用场景有访问控制、防火墙、内容过滤、Web缓存、内容路由等等。

为什么要爬取历史文章

微信官方其实已经提过了素材列表接口

https://developers.weixin.qq.com/doc/offiaccount/Asset_Management/Get_materials_list.html

但是接口获取的素材地址并不是真实在公众号上推送的地址,所以不存在阅读、评论、好看等功能。
这时候需要我们去公众号的历史文章页取数据。

实现步骤

开发环境:Visual Studio Community 2019 for Mac Version 8.5.6 (build 11)
Frameworks:.NET Core 3.1.0
NuGet:Newtonsoft.Json 12.0.3、Titanium.Web.Proxy 3.1.1301

大致思路

1、先实现通过代理抓包HTTPS,拦截微信客户端数据交互。
2、过滤其他地址,只监测微信文章。
3、访问任意微信文章页,获取header和COOKIE。
4、模拟微信访问历史页、分析抓取历史文章列表。

核心代码

SpiderProxy.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using Newtonsoft.Json.Linq;
using Titanium.Web.Proxy;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Models;


namespace WechatArticleSpider
{
 public class SpiderProxy
 {
  private readonly SemaphoreSlim @lock = new SemaphoreSlim(1);
  private readonly ProxyServer proxyServer;
  private readonly ExplicitProxyEndPoint explicitEndPoint;


  public SpiderProxy()
  {
   proxyServer = new ProxyServer();
   //在响应之前事件
   proxyServer.BeforeResponse += ProxyServer_BeforeResponse;


   //绑定监听端口
   explicitEndPoint = new ExplicitProxyEndPoint(IPAddress.Any, 8000, true);
   Console.WriteLine("监听地址127.0.0.1:8000");
   //隧道请求连接前事件,HTTPS用
   explicitEndPoint.BeforeTunnelConnectRequest += ExplicitEndPoint_BeforeTunnelConnectRequest; ;


   //代理服务器注册监听地址
   proxyServer.AddEndPoint(explicitEndPoint);
  }


  public void Start()
  {
   Console.WriteLine("开始监听");
   //Start方法会检测证书,若为空会调用CertificateManager.EnsureRootCertificate();为我们自动生成根证书。
   proxyServer.Start();
  }


  public void Stop()
  {
   // Unsubscribe & Quit
   explicitEndPoint.BeforeTunnelConnectRequest -= ExplicitEndPoint_BeforeTunnelConnectRequest;
   proxyServer.BeforeResponse -= ProxyServer_BeforeResponse;


   Console.WriteLine("结束监听");
   proxyServer.Stop();
  }


  private async Task ExplicitEndPoint_BeforeTunnelConnectRequest(object sender, Titanium.Web.Proxy.EventArguments.TunnelConnectSessionEventArgs e)
  {
   string hostname = e.HttpClient.Request.RequestUri.Host;
   await WriteToConsole("Tunnel to: " + hostname);


   if (!hostname.StartsWith("mp.weixin.qq.com"))
   {
    //是否要解析SSL,不解析就直接转发
    e.DecryptSsl = false;
   }
  }


  private async Task ProxyServer_BeforeResponse(object sender, Titanium.Web.Proxy.EventArguments.SessionEventArgs e)
  {
   var request = e.HttpClient.Request;


   //判断是否是微信文章页。
   if (request.Host.Contains("mp.weixin.qq.com")&&(request.RequestUriString.StartsWith("/s?") || request.RequestUriString.StartsWith("/s/")))
   {
    byte[] bytes = await e.GetResponseBody();
    string body = System.Text.Encoding.UTF8.GetString(bytes);
    ThreadPool.QueueUserWorkItem((stateInfo) => { CrawlAsync(body, e.HttpClient.Request); }); 
   }
  }


  private async Task CrawlAsync(string body,Request request)
  {
   //采用正则表达式匹配数据
   Match match = Regex.Match(body, @"(.+)");
   Match matchGhid = Regex.Match(body, @"var user_name = ""(.+)"";");
   if (!match.Success || !matchGhid.Success)
   {
    return;
   }


   MatchCollection matches = Regex.Matches(body, @"(.+)");
   if (match.Groups.Count == 0)
   {
    return;
   }
   await WriteToConsole("检测到微信文章页: " + match.Groups[1].Value + " " + matches[0].Groups[1].Value + " " + matches[1].Groups[1].Value + " ");


   var queryString = HttpUtility.ParseQueryString(request.RequestUriString.Substring(3));
   var httpClient = new HttpClient(request.Headers);
   await WriteToConsole("Client实例化,已获取header,COOKIE");


   //获取历史页信息
   string result = httpClient.Get(string.Format("https://mp.weixin.qq.com/mp/profile_ext?action=home&__biz={0}&scene=124#wechat_redirect", queryString["__biz"]));
   //封接口检测
   if (result.Contains("操作频繁,请稍后再试"))
   {
    await WriteToConsole("操作频繁,请稍后再试 限制24小时 请更换微信");
    await WriteToConsole("已停止爬虫任务,请更换之后重启助手。");
   }


   //是否抓取完
   bool end = false;
   //下标
   int offset = 0;


   do
   {
    //获取历史消息
    string jsOnResult= httpClient.Get(string.Format("https://mp.weixin.qq.com/mp/profile_ext?action=getmsg&__biz={0}&f=json&offset={1}&count=10&is_ok=1&scene=124&uin={2}&key={3}&pass_ticket={4}&wxtoken=&x5=0&f=json", queryString["__biz"], offset, queryString["uin"], queryString["key"], queryString["pass_ticket"]));
    JObject jobject = JObject.Parse(jsonResult);
    if (Convert.ToInt32(jobject["ret"]) == 0)
    {
     if (Convert.ToInt32(jobject["can_msg_continue"]) == 0)
     {
      end = true;
     }
     offset = Convert.ToInt32(jobject["next_offset"]);
     string strList = jobject["general_msg_list"].ToString();
     JObject temp = JObject.Parse(strList);
     List list = temp["list"].ToList();
     foreach (var item in list)
     {
      JToken comm_msg_info = item["comm_msg_info"];
      JToken app_msg_ext_info = item["app_msg_ext_info"];
      if (app_msg_ext_info == null)
      {
       continue;
      }
      //发布时间
      string publicTime = comm_msg_info["datetime"].ToString();
      //文章标题
      string title = app_msg_ext_info["title"].ToString();
      //文章摘要
      string digest = app_msg_ext_info["digest"].ToString();
      //文章地址
      string content_url = app_msg_ext_info["content_url"].ToString();
      //文章封面
      string cover = app_msg_ext_info["cover"].ToString();
      //作者
      string author = app_msg_ext_info["author"].ToString();


      await WriteToConsole(String.Format("{0},{1},{2},{3},{4},{5}", publicTime, title, digest, content_url, cover, author));


      //今天发布了多条消息
      if (app_msg_ext_info["is_multi"].ToString() == "1")
      {
       foreach (var multiItem in app_msg_ext_info["multi_app_msg_item_list"].ToList())
       {
        title = multiItem["title"].ToString();
        digest = multiItem["digest"].ToString();
        content_url = multiItem["content_url"].ToString();
        cover = multiItem["cover"].ToString();
        author = multiItem["author"].ToString();
        await WriteToConsole(String.Format("{0},{1},{2},{3},{4},{5}", publicTime, title, digest, content_url, cover, author));
       }
      }
     }
    }
    else
    {
     end = true;
    }
    //每5秒翻页一次
    await Task.Delay(5000);
   } while (!end);


   await WriteToConsole("历史文章抓取完成");


  }


  private async Task WriteToConsole(string message, ConsoleColor? cOnsoleColor= null)
  {
   await @lock.WaitAsync();


   if (consoleColor.HasValue)
   {
    ConsoleColor existing = Console.ForegroundColor;
    Console.ForegroundColor = consoleColor.Value;
    Console.WriteLine(message);
    Console.ForegroundColor = existing;
   }
   else
   {
    Console.WriteLine(message);
   }


   @lock.Release();
  }
 }
}

HttpClient.cs

using System;
using System.IO;
using System.Net;
using System.Threading;
using Titanium.Web.Proxy.Http;


namespace WechatArticleSpider
{
 class HttpClient
 {
  private readonly HeaderCollection headerCollection;
  private readonly COOKIEContainer COOKIECOntainer= new COOKIEContainer();


  /// 
  /// 微信请求客户端
  /// 
  /// 拦截微信请求头集合
  public HttpClient(HeaderCollection headerCollection)
  {
   COOKIECOntainer= new COOKIEContainer();
   ServicePointManager.DefaultCOnnectionLimit= 512;
   ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11;
   this.headerCollection = headerCollection;
  }


  /// 
  /// 带微信参数的GET请求
  /// 
  /// 请求地址
  /// 请求结果
  public string Get(string url)
  {
   string ret = Retry(() =>
   {
    HttpWebRequest webRequest = WebRequest.CreateHttp(url);
    webRequest = PretendWechat(webRequest);
    HttpWebResponse respOnse= webRequest.GetResponse() as HttpWebResponse;
    string result = new StreamReader(response.GetResponseStream()).ReadToEnd();
    return result;
   });
   return ret;
  }


  /// 
  /// 伪造微信请求
  /// 
  /// 需要伪造的request
  /// 
  public HttpWebRequest PretendWechat(HttpWebRequest request)
  {
   try
   {
    request.Host = headerCollection.Headers["Host"].Value;
    request.UserAgent = headerCollection.Headers["User-Agent"].Value;
    request.Headers.Set(headerCollection.Headers["Accept-Language"].Name, headerCollection.Headers["Accept-Language"].Value);
    request.Headers.Set(headerCollection.Headers["Accept-Encoding"].Name, headerCollection.Headers["Accept-Encoding"].Value);
    COOKIEContainer.SetCOOKIEs(new Uri("https://mp.weixin.qq.com"), headerCollection.Headers["COOKIE"].Value.Replace(";", ","));
    request.KeepAlive = true;
    request.Accept = headerCollection.Headers["Accept"].Value;
    request.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip;
    request.COOKIECOntainer= COOKIEContainer;
    request.AllowAutoRedirect = true;
    request.ServicePoint.Expect100COntinue= false;
    request.Timeout = 35000;
    return request;
   }
   catch (Exception e)
   {
    Console.WriteLine(e.Message);
    throw;
   }
  }


  /// 
  /// 三次重试机制
  /// 
  /// 参数类型
  /// 方法
  /// 
  private static T Retry(Func func)
  {
   int err = 0;
   while (err <3)
   {
    try
    {
     return func();
    }
    catch (WebException webExp)
    {
     err++;
     Thread.Sleep(5000);
     if (err > 2)
     {
      throw webExp;
     }
    }
   }
   return func();
  }
 }
}

测试结果

首先我们主动设置一下系统代理。

接着启动代理。

对于不是目标地址的https请求,一律过滤直接转发。
此时Titanium应该会为我们生成了根证书。

右键-》Get Info-》Trust-》选择Always Trust,如果不信任根证书,会发现ProxyServer_BeforeResponse不执行。
最后我们随意的访问一篇公众号文章,代理就会执行脚本去抓公众号的历史文章列表了。

demo:链接:https://pan.baidu.com/s/1ZafgBH1dEiDcdB9E77osFg 密码:tuuv

以上就是c# 基于Titanium爬取微信公众号历史文章列表的详细内容,更多关于c# 基于Titanium爬取公众号历史文章的资料请关注其它相关文章!


推荐阅读
  • 使用正则表达式爬取36Kr网站首页新闻的操作步骤和代码示例
    本文介绍了使用正则表达式来爬取36Kr网站首页所有新闻的操作步骤和代码示例。通过访问网站、查找关键词、编写代码等步骤,可以获取到网站首页的新闻数据。代码示例使用Python编写,并使用正则表达式来提取所需的数据。详细的操作步骤和代码示例可以参考本文内容。 ... [详细]
  • Windows7企业版怎样存储安全新功能详解
    本文介绍了电脑公司发布的GHOST WIN7 SP1 X64 通用特别版 V2019.12,软件大小为5.71 GB,支持简体中文,属于国产软件,免费使用。文章还提到了用户评分和软件分类为Win7系统,运行环境为Windows。同时,文章还介绍了平台检测结果,无插件,通过了360、腾讯、金山和瑞星的检测。此外,文章还提到了本地下载文件大小为5.71 GB,需要先下载高速下载器才能进行高速下载。最后,文章详细解释了Windows7企业版的存储安全新功能。 ... [详细]
  • 篇首语:本文由编程笔记#小编为大家整理,主要介绍了常用#免费%代理IP库&整理*收藏——实时@更新(大概)相关的知识,希望对你有一定的参考价值。 ... [详细]
  • 禁止程序接收鼠标事件的工具_VNC Viewer for Mac(远程桌面工具)免费版
    VNCViewerforMac是一款运行在Mac平台上的远程桌面工具,vncviewermac版可以帮助您使用Mac的键盘和鼠标来控制远程计算机,操作简 ... [详细]
  • t-io 2.0.0发布-法网天眼第一版的回顾和更新说明
    本文回顾了t-io 1.x版本的工程结构和性能数据,并介绍了t-io在码云上的成绩和用户反馈。同时,还提到了@openSeLi同学发布的t-io 30W长连接并发压力测试报告。最后,详细介绍了t-io 2.0.0版本的更新内容,包括更简洁的使用方式和内置的httpsession功能。 ... [详细]
  • 使用在线工具jsonschema2pojo根据json生成java对象
    本文介绍了使用在线工具jsonschema2pojo根据json生成java对象的方法。通过该工具,用户只需将json字符串复制到输入框中,即可自动将其转换成java对象。该工具还能解析列表式的json数据,并将嵌套在内层的对象也解析出来。本文以请求github的api为例,展示了使用该工具的步骤和效果。 ... [详细]
  • 本文介绍了前端人员必须知道的三个问题,即前端都做哪些事、前端都需要哪些技术,以及前端的发展阶段。初级阶段包括HTML、CSS、JavaScript和jQuery的基础知识。进阶阶段涵盖了面向对象编程、响应式设计、Ajax、HTML5等新兴技术。高级阶段包括架构基础、模块化开发、预编译和前沿规范等内容。此外,还介绍了一些后端服务,如Node.js。 ... [详细]
  • 【shell】网络处理:判断IP是否在网段、两个ip是否同网段、IP地址范围、网段包含关系
    本文介绍了使用shell脚本判断IP是否在同一网段、判断IP地址是否在某个范围内、计算IP地址范围、判断网段之间的包含关系的方法和原理。通过对IP和掩码进行与计算,可以判断两个IP是否在同一网段。同时,还提供了一段用于验证IP地址的正则表达式和判断特殊IP地址的方法。 ... [详细]
  • 小程序自动授权和手动接入的方式及操作步骤
    本文介绍了小程序支持的两种接入方式:自动授权和手动接入,并详细说明了它们的操作步骤。同时还介绍了如何在两种方式之间切换,以及手动接入后如何下载代码包和提交审核。 ... [详细]
  • 分享css中提升优先级属性!important的用法总结
    web前端|css教程css!importantweb前端-css教程本文分享css中提升优先级属性!important的用法总结微信门店展示源码,vscode如何管理站点,ubu ... [详细]
  • 本文介绍了互联网思维中的三个段子,涵盖了餐饮行业、淘品牌和创业企业的案例。通过这些案例,探讨了互联网思维的九大分类和十九条法则。其中包括雕爷牛腩餐厅的成功经验,三只松鼠淘品牌的包装策略以及一家创业企业的销售额增长情况。这些案例展示了互联网思维在不同领域的应用和成功之道。 ... [详细]
  • 【技术分享】一个 ELF 蠕虫分析
    【技术分享】一个 ELF 蠕虫分析 ... [详细]
  • 我一直都有记录信息的习惯,不知是从什么时候开始,大约是在工作后不久。如今还真有点庆幸从那时开始记了点东西,当然是电子版的,写 ... [详细]
  • 问题描述:域名已经备案,我全部都有,也在后台配置了,但是手机预览,还是请求失败,PC端是可以请求 ... [详细]
  •   一、GeoTrust证书的相关介绍    GeoTrust成立于2001年,其到2006年就占领了全球市场25%的市场份额,所以GeoTrust是目前全球第二大的数字证书颁发机 ... [详细]
author-avatar
林秋伟左婷_894
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有