基于搜狗搜索的微信公众号爬虫实现(C#版本)
Author: Hoyho Luo
Email: luohaihao@gmail.com
Source Url:https://here2say.tw/11/
转载请保留此出处
本文介绍基于搜狗的微信公众号定向爬虫,使用C#实现,故取名WeGouSharp。本文中的项目托管在Github上,你可以戳WeGouSharp获取源码,欢迎点星。关于微信公共号爬虫的项目网上已经不少,然而基本大多数的都是使用Python实现 鉴于鄙人是名.NET开发人员,于是又为广大微软系同胞创建了这个轮子,使用C#实现的微信爬虫 蓝本为Chyroc/WechatSogou, 在此还请各位大佬指教。
目录1.项目结构
2.数据结构
3.xpath介绍
4.使用HtmlAgilityPack解析网页内容
5.验证码处理以及文件缓存
一、 项目结构
如下图
API类:
所有直接的操作封装好在API类中,直接使用里面的方法
Install-Package HtmlAgilityPack
二、 数据结构
本项目根据微信公账号以及搜狗搜索定义了多个结构,可以查看模型类,主要包括以下:
公众号结构:
public struct OfficialAccount{public string AccountPageurl;public string WeChatId;public string Name;public string Introduction;public bool IsAuth; public string QrCode;public string ProfilePicture;//public string RecentArticleUrl;}
字段含义
字段 | 含义 |
---|---|
AccountPageurl | 微信公众号页 |
WeChatId | 公号ID(唯一) |
Name | 名称 |
Introduction | 介绍 |
IsAuth | 是否官方认证 |
QrCode | 二维码链接 |
ProfilePicture | 头像链接 |
公号群发消息结构(含图文推送)
public struct BatchMessage{public int Meaasgeid;public string SendDate;public string Type; //49:图文,1:文字,3:图片,34:音频,62:视频public string Content; public string ImageUrl; public string PlayLength;public int FileId;public string AudioSrc;//for type 图文public string ContentUrl;public int Main;public string Title;public string Digest;public string SourceUrl;public string Cover;public string Author;public string CopyrightStat;//for type 视频public string CdnVideoId;public string Thumb;public string VideoSrc;//others}
字段含义
字段 | 含义 |
---|---|
Meaasgeid | 消息号 |
SendDate | 发出时间(unix时间戳) |
Type | 消息类型:49:图文, 1:文字, 3:图片, 34:音频, 62:视频 |
Content | 文本内容(针对类型1即文字) |
ImageUrl | 图片(针对类型3,即图片) |
PlayLength | 播放长度(针对类型34,即音频,下同) |
FileId | 音频文件id |
AudioSrc | 音频源 |
ContentUrl | 文章来源(针对类型49,即图文,下同) |
Main | 不明确 |
Title | 文章标题 |
Digest | 不明确 |
SourceUrl | 可能是阅读原文 |
Cover | 封面图 |
Author | 作者 |
CopyrightStat | 可能是否原创? |
CdnVideoId | 视频id(针对类型62,即视频,下同) |
Thumb | 视频缩略图 |
VideoSrc | 视频链接 |
文章结构
public struct Article{public string Url;public List<string>Imgs;public string Title;public string Brief;public string Time;public string ArticleListUrl;public OfficialAccount officialAccount;}
字段含义
字段 | 含义 |
---|---|
Url | 文章链接 |
Imgs | 封面图(可能多个) |
Title | 文章标题 |
Brief | 简介 |
Time | 发表日期(unix时间戳) |
OfficialAccount | 关联的公众号(信息不全,仅供参考) |
搜索榜结构
public struct HotWord{public int Rank;//排行public string Word;public string JumpLink; //相关链接public int HotDegree; //热度}
三 、xpath介绍
什么是 XPath?
- XPath 使用路径表达式在 XML 文档中进行导航
- XPath 包含一个标准函数库
- XPath 是 XSLT 中的主要元素
- XPath 是一个 W3C 标准
简而言之,Xpath是XML元素的位置,下面是W3C教程时间,老鸟直接跳过
XML 实例文档
我们将在下面的例子中使用这个 XML 文档。
<?xml version="1.0" encoding="ISO-8859-1"?><bookstore><book><title lang="eng">Harry Potter</title><price>29.99</price> </book><book><title lang="eng">Learning XML</title><price>39.95</price> </book></bookstore>
选取节点
XPath 使用路径表达式在 XML 文档中选取节点。节点是通过沿着路径或者 step 来选取的。
下面列出了最有用的路径表达式:
表达式 | 描述 |
---|---|
nodename | 选取此节点的所有子节点。 |
/ | 从根节点选取。 |
// | 从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置。 |
. | 选取当前节点。 |
.. | 选取当前节点的父节点。 |
@ | 选取属性。 |
实例
在下面的表格中,我们已列出了一些路径表达式以及表达式的结果:
路径表达式 | 结果 |
---|---|
bookstore | 选取 bookstore 元素的所有子节点。 |
/bookstore |
选取根元素 bookstore。 注释:假如路径起始于正斜杠( / ),则此路径始终代表到某元素的绝对路径! |
bookstore/book | 选取属于 bookstore 的子元素的所有 book 元素。 |
//book | 选取所有 book 子元素,而不管它们在文档中的位置。 |
bookstore//book | 选择属于 bookstore 元素的后代的所有 book 元素,而不管它们位于 bookstore 之下的什么位置。 |
//@lang | 选取名为 lang 的所有属性。 |
谓语(Predicates)
谓语用来查找某个特定的节点或者包含某个指定的值的节点。
谓语被嵌在方括号中。
实例
在下面的表格中,我们列出了带有谓语的一些路径表达式,以及表达式的结果:
路径表达式 | 结果 |
---|---|
/bookstore/book[1] | 选取属于 bookstore 子元素的第一个 book 元素。 |
/bookstore/book[last()] | 选取属于 bookstore 子元素的最后一个 book 元素。 |
/bookstore/book[last()-1] | 选取属于 bookstore 子元素的倒数第二个 book 元素。 |
/bookstore/book[position()<3] | 选取最前面的两个属于 bookstore 元素的子元素的 book 元素。 |
//title[@lang] | 选取所有拥有名为 lang 的属性的 title 元素。 |
//title[@lang='eng'] | 选取所有 title 元素,且这些元素拥有值为 eng 的 lang 属性。 |
/bookstore/book[price>35.00] | 选取 bookstore 元素的所有 book 元素,且其中的 price 元素的值须大于 35.00。 |
/bookstore/book[price>35.00]/title | 选取 bookstore 元素中的 book 元素的所有 title 元素,且其中的 price 元素的值须大于 35.00。 |
选取未知节点
XPath 通配符可用来选取未知的 XML 元素。
通配符 | 描述 |
---|---|
* | 匹配任何元素节点。 |
@* | 匹配任何属性节点。 |
node() | 匹配任何类型的节点。 |
实例
在下面的表格中,我们列出了一些路径表达式,以及这些表达式的结果:
路径表达式 | 结果 |
---|---|
/bookstore/* | 选取 bookstore 元素的所有子元素。 |
//* | 选取文档中的所有元素。 |
//title[@*] |
选取所有带有属性的 title 元素。
来源: http://www.w3school.com.cn/xpath/xpath_syntax.asp
|
如图,假设我要抓取首页一个banner图,可以在chrome按下F12参考该元素的Xpath,
即该图片对应的Xpth为: //*[@id="loginWrap"]/div[4]/div[1]/div[1]/div/a[4]/img
四、 使用HtmlAgilityPack解析网页内容
HttpTool类里封装了一个较多参数的HTTP GET操作,用于获取搜狗的页面:
因为搜狗本身是做搜索引擎的缘故,所以反爬虫是非常严厉的,因此HTTP GET的方法要注意携带很多参数,且不同页面要求不一样.一般地,要带上默认的
referer和host 然后请求头的UserAgent 要伪造,常用的useragent有
public static List<string> _agent = new List<string> { "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; AcooBrowser; .NET CLR 1.1.4322; .NET CLR 2.0.50727)", "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; Acoo Browser; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; .NET CLR 3.0.04506)", "Mozilla/4.0 (compatible; MSIE 7.0; AOL 9.5; AOLBuild 4337.35; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)", "Mozilla/5.0 (Windows; U; MSIE 9.0; Windows NT 9.0; en-US)", "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 2.0.50727; Media Center PC 6.0)", "Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 1.0.3705; .NET CLR 1.1.4322)", "Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.2; .NET CLR 1.1.4322; .NET CLR 2.0.50727; InfoPath.2; .NET CLR 3.0.04506.30)", "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN) AppleWebKit/523.15 (KHTML, like Gecko, Safari/419.3) Arora/0.3 (Change: 287 c9dfb30)", "Mozilla/5.0 (X11; U; Linux; en-US) AppleWebKit/527+ (KHTML, like Gecko, Safari/419.3) Arora/0.6", "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.2pre) Gecko/20070215 K-Ninja/2.1.1", "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9) Gecko/20080705 Firefox/3.0 Kapiko/3.0", "Mozilla/5.0 (X11; Linux i686; U;) Gecko/20070322 Kazehakase/0.4.5", "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.8) Gecko Fedora/1.9.0.8-1.fc10 Kazehakase/0.5.6", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/535.20 (KHTML, like Gecko) Chrome/19.0.1036.7 Safari/535.20", "Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; fr) Presto/2.9.168 Version/11.52", };
自定义的GET 方法
/// <summary>/// 指定header参数的HTTP Get方法/// </summary>/// <param name="headers"></param>/// <param name="url"></param>/// <returns>respondse</returns>public string Get(WebHeaderCollection headers, string url ,string responseEncoding="UTF-8",bool isUseCookie = false){string responseText = "";try{var request = (HttpWebRequest)WebRequest.Create(url);request.Method = "GET";foreach (string key in headers.Keys){switch (key.ToLower()){case "user-agent":request.UserAgent = headers[key];break;case "referer":request.Referer = headers[key];break;case "host":request.Host = headers[key];break;case "contenttype":request.ContentType = headers[key];break;case "accept":request.Accept = headers[key];break;default:break;}}if (string.IsNullOrEmpty(request.Referer)){request.Referer = "http://weixin.sogou.com/";};if (string.IsNullOrEmpty(request.Host)){request.Host = "weixin.sogou.com";};if (string.IsNullOrEmpty(request.UserAgent)){Random r = new Random();int index = r.Next(WechatSogouBasic._agent.Count - 1);request.UserAgent = WechatSogouBasic._agent[index];}if (isUseCookie){CookieCollection cc = Tools.LoadCookieFromCache();request.CookieContainer = new CookieContainer();request.CookieContainer.Add(cc);}HttpWebResponse response = (HttpWebResponse)request.GetResponse();if (isUseCookie && response.Cookies.Count >0){var cookieCollection = response.Cookies;WechatCache cache = new WechatCache(Config.CacheDir, 3000);if (!cache.Add("cookieCollection", cookieCollection, 3000)) { cache.Update("cookieCollection", cookieCollection, 3000); };}// Get the stream containing content returned by the server.Stream dataStream = response.GetResponseStream();//如果response是图片,则返回以base64方式返回图片内容,否则返回html内容if (response.Headers.Get("Content-Type") == "image/jpeg" || response.Headers.Get("Content-Type") == "image/jpg"){Image img = Image.FromStream(dataStream, true);using (MemoryStream ms = new MemoryStream()){// Convert Image to byte[]//img.Save("myfile.jpg"); img.Save(ms,System.Drawing.Imaging.ImageFormat.Jpeg);byte[] imageBytes = ms.ToArray();// Convert byte[] to Base64 Stringstring base64String = Convert.ToBase64String(imageBytes);responseText = base64String;}}else //read response string {// Open the stream using a StreamReader for easy access. Encoding encoding;switch (responseEncoding.ToLower()){case "utf-8":encoding = Encoding.UTF8;break;case "unicode":encoding = Encoding.Unicode;break;case "ascii":encoding = Encoding.ASCII;break;default:encoding = Encoding.Default;break;}StreamReader reader = new StreamReader(dataStream, encoding);//System.Text.Encoding.Default// Read the content.if (response.StatusCode == HttpStatusCode.OK){responseText = reader.ReadToEnd();if (responseText.Contains("用户您好,您的访问过于频繁,为确认本次访问为正常用户行为,需要您协助验证")){_vcode_url = url;throw new Exception("weixin.sogou.com verification code");}}else{logger.Error("requests status_code error" + response.StatusCode);throw new Exception("requests status_code error");}reader.Close();}dataStream.Close();response.Close();}catch (Exception e){logger.Error(e);}return responseText;}
前面关于Xpath废话太多,直接上一个案例,解析公众号页面:
public List<OfficialAccount> SearchOfficialAccount(string keyword, int page = 1){List<OfficialAccount> accountList = new List<OfficialAccount>();string text = this._SearchAccount_Html(keyword, page);//返回了一个搜索页面的html代码HtmlDocument pageDoc = new HtmlDocument();pageDoc.LoadHtml(text);HtmlNodeCollection targetArea = pageDoc.DocumentNode.SelectNodes("//ul[@class='news-list2']/li");if (targetArea != null){foreach (HtmlNode node in targetArea){try{OfficialAccount accountInfo = new OfficialAccount();//链接中包含了& html编码符,要用htmdecode,不是urldecodeaccountInfo.AccountPageurl = WebUtility.HtmlDecode(node.SelectSingleNode("div/div[@class='img-box']/a").GetAttributeValue("href", ""));//accountInfo.ProfilePicture = node.SelectSingleNode("div/div[1]/a/img").InnerHtml;accountInfo.ProfilePicture = WebUtility.HtmlDecode(node.SelectSingleNode("div/div[@class='img-box']/a/img").GetAttributeValue("src", ""));accountInfo.Name = node.SelectSingleNode("div/div[2]/p[1]").InnerText.Trim().Replace("<!--red_beg-->", "").Replace("<!--red_end-->", "");accountInfo.WeChatId = node.SelectSingleNode("div/div[2]/p[2]/label").InnerText.Trim();accountInfo.QrCode = WebUtility.HtmlDecode(node.SelectSingleNode("div/div[3]/span/img").GetAttributeValue("src", ""));accountInfo.Introduction = node.SelectSingleNode("dl[1]/dd").InnerText.Trim().Replace("<!--red_beg-->","").Replace("<!--red_end-->", "");//早期的账号认证和后期的认证显示不一样?,对比 bitsea 和 NUAA_1952 两个账号//现在改为包含该script的即认证了if (node.InnerText.Contains("document.write(authname('2'))")){accountInfo.IsAuth = true;}else{accountInfo.IsAuth = false;}accountList.Add(accountInfo);}catch (Exception e){logger.Warn(e);}}}return accountList; }
五 、验证码处理以及文件缓存
/// <summary>/// 页面出现验证码,输入才能继续,此验证依赖cookie, 获取验证码的requset有个cookie,每次不同,需要在post验证码的时候带上/// </summary>/// <returns></returns>public bool VerifyCodeForContinute(string url ,bool isUseOCR){bool isSuccess = false;logger.Debug("vcode appear, use VerifyCodeForContinute()");DateTime Epoch = new DateTime(1970, 1, 1,0,0,0,0);var timeStamp17 = (DateTime.UtcNow - Epoch).TotalMilliseconds.ToString("R"); //get timestamp with 17 bitstring codeurl = "https://mp.weixin.qq.com/mp/verifycode?cert=" + timeStamp17;WebHeaderCollection headers = new WebHeaderCollection();var content = this.Get(headers, codeurl,"UTF-8",true);ShowImageHandle showImageHandle = new ShowImageHandle(DisplayImageFromBase64);showImageHandle.BeginInvoke(content, null, null);Console.WriteLine("请输入验证码:");string verifyCode = Console.ReadLine();string postURL = "https://mp.weixin.qq.com/mp/verifycode";timeStamp17 = (DateTime.UtcNow - Epoch).TotalMilliseconds.ToString("R"); //get timestamp with 17 bitstring postData = string.Format("cert={0}&input={1}",timeStamp17,verifyCode );// "{" + string.Format(@"'cert':'{0}','input':'{1}'", timeStamp17, verifyCode) + "}";headers.Add("Host", "mp.weixin.qq.com");headers.Add("Referer", url);string remsg = this.Post(postURL, headers, postData,true);try{JObject jo = JObject.Parse(remsg);//把json字符串转化为json对象 int statusCode = (int)jo.GetValue("ret");if (statusCode == 0){isSuccess = true;}else{logger.Error("cannot unblock because " + jo.GetValue("msg"));var vcodeException = new WechatSogouVcodeException();vcodeException.MoreInfo = "cannot jiefeng because " + jo.GetValue("msg");throw vcodeException;}}catch(Exception e){logger.Error(e);}return isSuccess;}
var timeStamp17 = (DateTime.UtcNow - Epoch).TotalMilliseconds.ToString("R"); //get timestamp with 17 bit
CookieCollection cc = Tools.LoadCookieFromCache(); request.CookieContainer = new CookieContainer(); request.CookieContainer.Add(cc);
六、后话
转载于:https://www.cnblogs.com/hoyho/p/7623903.html
基于搜狗搜索的微信公众号爬虫实现(C#版本)相关推荐
- 使用搜狗接口对微信公众号爬虫
搜狗搜索因为有微信公众号搜索的接口,所以通过这个接口就可以实现公众号的爬虫 需要安装几个python的库:selenium,pyquery 还使用到phantomjs.exe,这个需要我们自己去下载, ...
- 基于搜狗接口的微信公众号及其信息爬取
1.思路 经测试,搜狗搜索提供的微信公众号的接口是理所当然爬取多了会被封ip等方式重点照顾.这只是做一个公众号及其链接的爬取,公众号的内容爬取及制定内容的爬取都是一个路子.搞懂了一个其他的就都差不多了 ...
- 基于Python实现微信公众号爬虫进行数据分析
学爬虫有什么用 网络爬虫是一个非常注重实践性而且实用性很强的编程技能,它不是程序员的专属技能,任何具有一定编程基础的人都可以学习爬虫,写爬虫分析股票走势,上链家爬房源分析房价趋势,爬知乎.爬豆瓣.爬新 ...
- 【Python爬虫实战】微信公众号爬虫:微信公众号浏览自动化
本文内容详细介绍微信公众号历史文章自动化浏览脚本的实现,配合服务端对公众号文章数据爬取来实现微信公众号文章数据的采集.服务端爬取实现见:微信公众号爬虫:服务端公众号文章数据采集 背景:在团队的学习方面 ...
- Google缩进40%搜索结果,微信公众号/今日头条开放搜索引擎
如图显示,Google已有40%搜索结果是缩进展示. 之前提过的Google新展现形式-缩进搜索结果,现在有了统计数据.已经有40%的搜索结果是缩进结果,没想到比例这么大了28%有一个缩进结果,8%有 ...
- 微信公众号爬虫项目(reptile)
代码仓库地址:reptile: 爬虫项目,微信公众号文章爬虫,网站文章爬虫,群发邮件系统 项目背景 个人在业余时间,写的一个以微信公众号爬虫为主要功能,普通网页爬虫.浏览器控制.邮件群发功能为辅的简单 ...
- 【开源Python爬虫】微信公众号爬虫weixin_crawler开源啦
作者 | 抽丝剥茧 出品 | 爱迪斯 微信公众号爬虫weixin_crawler开源啦 正式介绍weixin_crawler之前,我准备了两个问题,这两个问题通过weixin_crawler自带的报告 ...
- springboot 微信太阳码_WxJava基于Spring Boot开发微信公众号手机注册码
WxJava基于Spring Boot开发微信公众号手机注册码 Szx • 2019 年 05 月 18 日 第一步先下载官方Demo https://github.com/binarywang/we ...
- 软件作业(3):用户体验分析——基于南通大学教务管理系统微信公众号
用户体验分析--基于南通大学教务管理系统微信公众号 1. 目标:基于实例分析,体会用户体验设计的 7 条准则. 2. 要求 ①基于我们列出的 7 条UX评价准则,分析"南通大学教务管理系统微 ...
- 利用搜狗抓取微信公众号文章
微信一直是一个自己玩的小圈子,前段时间搜狗推出的微信搜索带来了一丝曙光.搜狗搜索推出了内容搜索和公众号搜索两种,利用后者可以抓取微信公众号的最新内容,看了下还是比较及时的. 每个公众号都有一个open ...
最新文章
- 工业相机帧率与曝光时间的关系
- IDC运维团队技术交流总结篇————换个角度看世界
- jsp2自定义标签开篇
- flask 对excel上传下载操作和文件处理
- python3中format函数列表_Python3之字符串格式化format函数详解(上)
- java判断long 大于0_JAVA的一道习题:找出五个大于Long.MAX_VALUE的素数
- 70多个国家地区免费享受wifi
- Python打包文件夹(zip/tar/tar.gz)
- 【ACO MTSP】基于matlab粒子群优化蚁群算法求解多旅行商问题【含Matlab源码 1616期】
- CAD制图快捷键分享,制图之前的基本准备
- 安卓终端模拟器运行PHP,ROOT用户福利:安卓终端模拟器简述及命令行入门!
- python识别条形码_用 Python 和 OpenCV 检测图片上的条形码
- 单调队列java_单调队列单调栈
- LCD12864 菜单部分编写
- mac SCp上传文件到阿里云服务器centos
- 自车坐标系下的物体相对和绝对位置和速度计算
- 字符串——BZOJ 3097: Hash Killer I【构造题,思维题】
- 汇编程序设计:代码转换程序
- VSCode Tab按键失效,无法自动补全是什么情况?
- 韩顺平c语言视频笔记,韩顺平视频笔记
热门文章
- Debian上安装 Proxmox VE 7
- 焊工双证是哪两证?考焊工证大概需要多少钱?
- Python3,一行代码实现文件夹共享,看到结果我酸了~
- android studio anr,Android ANR 分析
- Word 标题后自动添加分页符、分节符显示与查看
- android dismiss方法,Android Dialog.dismiss()与Activity.finish()顺序
- 用计算机怎么谈黑人团队,光遇黑人抬棺乐谱怎么弹奏 计算机演奏乐谱16
- java workbook.close_找不到符号Workbook.close()
- (转)C# 温故而知新:Stream篇(五)
- 多维特征输入,多层神经网络学习