基于搜狗搜索的微信公众号爬虫实现(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 元素。 |
如图,假设我要抓取首页一个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 - );
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 >)
{
var cookieCollection = response.Cookies;
WechatCache cache = new WechatCache(Config.CacheDir, );
if (!cache.Add("cookieCollection", cookieCollection, )) { cache.Update("cookieCollection", cookieCollection, ); };
}
// 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 String
string 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 = )
{
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,不是urldecode
accountInfo.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(, , ,,,,);
var timeStamp17 = (DateTime.UtcNow - Epoch).TotalMilliseconds.ToString("R"); //get timestamp with 17 bit
string 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 bit
string 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 == )
{
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 bitCookieCollection cc = Tools.LoadCookieFromCache();
request.CookieContainer = new CookieContainer();
request.CookieContainer.Add(cc);
六、后话
基于搜狗搜索的微信公众号爬虫实现(C#版本)的更多相关文章
- 用java实现的微信公众号爬虫
Published: 2016-11-23 In Spider. tags: Spider 版权声明:本文为博主原创文章,未经博主允许不得转载. 思路: 直接从chuansong.me爬取,由于微信公 ...
- 微信公众号接口类(PHP版本)
[项目需求] 通过微信提供的接口,实现微信公众号与后端的应用程序数据交互.消息响应等功能. [项目疑难点] 理解接口工作方式,统一接口API,响应速度.安全性等 [代码举例] WeixinAp ...
- 基于 muse-ui 封装一个微信公众号上传插件 实现多图上传
Vue.component('my-wx-upload', { template: ` <mu-grid-list :cols="3" :cellHeight="9 ...
- 使用Python爬取微信公众号文章并保存为PDF文件(解决图片不显示的问题)
前言 第一次写博客,主要内容是爬取微信公众号的文章,将文章以PDF格式保存在本地. 爬取微信公众号文章(使用wechatsogou) 1.安装 pip install wechatsogou --up ...
- python爬搜狗微信获取指定微信公众号的文章
前言: 之前收藏了一个叫微信公众号的文章爬取,里面用到的模块不错.然而 偏偏报错= =.果断自己写了一个 正文: 第一步爬取搜狗微信搜到的公众号: http://weixin.sogou.com/we ...
- python爬取微信公众号
爬取策略 1.需要安装python selenium模块包,通过selenium中的webdriver驱动浏览器获取Cookie的方法.来达到登录的效果 pip3 install selenium c ...
- 第三百三十节,web爬虫讲解2—urllib库爬虫—实战爬取搜狗微信公众号—抓包软件安装Fiddler4讲解
第三百三十节,web爬虫讲解2—urllib库爬虫—实战爬取搜狗微信公众号—抓包软件安装Fiddler4讲解 封装模块 #!/usr/bin/env python # -*- coding: utf- ...
- 九 web爬虫讲解2—urllib库爬虫—实战爬取搜狗微信公众号—抓包软件安装Fiddler4讲解
封装模块 #!/usr/bin/env python # -*- coding: utf-8 -*- import urllib from urllib import request import j ...
- [Python爬虫] 之十五:Selenium +phantomjs根据微信公众号抓取微信文章
借助搜索微信搜索引擎进行抓取 抓取过程 1.首先在搜狗的微信搜索页面测试一下,这样能够让我们的思路更加清晰 在搜索引擎上使用微信公众号英文名进行“搜公众号”操作(因为公众号英文名是公众号唯一的,而中文 ...
随机推荐
- [2017-2018上Java助教]个人信息收集
在本学期的Java课程中,我们要收集的信息如下 1.学号 .码云地址 3.博客园地址 请各位同学自行创建,并按照如下的格式评论在这篇博客下方 学号+https://git.oschina.net/as ...
- Swift_语法的熟悉
Swift_语法的熟悉 oc与swift区变 文件区别oc有实现文件以及声明文件等,在swift中实现机声明类似文件总归结一个即可 1. 常量与变量的初始化 常量 关键字"let" ...
- Spring中ApplicationContext加载机制
详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcytp33 加载器目前有两种选择:ContextLoaderListener和Co ...
- python怎么导入自定义函数
python 编程中经常需要调用自己定义的函数,在大型程序中自定义的函数一般会和main函数分开,这么主要讲下在不同文件下定义的函数怎么调用: 首先在有文件夹test_python文件夹下有main. ...
- 沙盒单机网站代表-Steam【推荐】
Steam平台是Valve公司聘请BitTorrent(BT下载)发明者布拉姆·科恩亲自开发设计的游戏平台. Steam平台目前是一款全球最大的综合性数字发行平台.玩家可以在该平台购买.下载.讨论.上 ...
- 转: 【Java并发编程】之二十一:并发新特性—阻塞队列和阻塞栈(含代码)
转载请注明出处:http://blog.csdn.net/ns_code/article/details/17511147 阻塞队列 阻塞队列是Java5并发新特性中的内容,阻塞队列的接口是Java. ...
- unity中object 对象之间用c# delegate方式进行通信
unity 3D经常需要设计到不同object之间数据通信和事件信息触发.这里可以利用C#本身的事件和代理的方法来实现. 这里实现了在GUI上点击按钮,触发事件,移动object cube移动的例子. ...
- 1st_homework_SE--四则运算题目生成器
0x00 Code 查询源代码及README请点此 0x01 需求分析 实现一个自动生成小学四则运算题目的命令行程序. 0x02 功能设计 主要功能为: 接受用户输入以便知道要出多少道题目python ...
- 201521123022 《Java程序设计》 第二周学习总结
1. 本章学习收获 (1)在老师指导下学会如何使用码云管理代码,代码不仅是保存到本地,还需要Push到码云这个"仓库"里. (2)JDK源代码可以为我们的编程提供许多便利之处,应善 ...
- 201521145048《Java程序设计管理》第一周学习总结
1. 本周学习总结 学习并了解Java的发展与历史 在网上视频中学习Java 了解并区分JVM JRE JDK 将java与已学语言做比较,发现相同处 2. 书面作业 Q1.为什么java程序可以跨平 ...