目前我在从事NET项目开发工作,此次我是使用C#来实现的微信爬虫。虽然目前关于实现微信爬虫的技术有很多但是大部分都是通过python来实现的,利用C#来实现的我相信还是在少数的,所以我觉得我开发的这个还是有看头的。同时也希望潜伏于论坛的各位大佬都能前来冒个泡,多多指导!
一、 项目结构
如下图
API类:
所有直接的操作封装好在API类中,直接使用里面的方法
Basic类:
主要处理逻辑
FileCache:
主要出现验证码的时候需要使用Ccokie验证身份,此类可以加密后序列化保存UIN,BIZ,cookie等内容以供后续使用
HttpHelper类:
网络请求,包括图片
Tools类:
图片处理,cookie加载等
依赖包可以直接使用package文件夹的版本
也可以自行在NuGet添加 如(visual studio-->tools-->Nuget Package Manager-->Package Manager Console):
- <font face="微软雅黑">Install-Package HtmlAgilityPack</font>
二、 数据结构
本项目根据微信公账号以及搜狗搜索定义了多个结构,可以查看模型类,主要包括以下:
公众号结构:
- <font face="微软雅黑">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;
- } </font>
公号群发消息结构(含图文推送)
- <font face="微软雅黑">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
- }</font>
字段含义
文章结构
- <font face="微软雅黑"> 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;
- }</font>
字段含义
搜索榜结构
- <font face="微软雅黑">public struct HotWord
- {
- public int Rank;//排行
- public string Word;
- public string Jumplink; //相关链接
- public int HotDegree; //热度</font>
三 、xpath介绍
什么是 XPath?
XPath 使用路径表达式在 XML 文档中进行导航
XPath 包含一个标准函数库
XPath 是 XSLT 中的主要元素
XPath 是一个 W3C 标准
简而言之,Xpath是XML元素的位置,下面是W3C教程时间,老鸟直接跳过
XML 实例文档
我们将在下面的例子中使用这个 XML 文档。
- <font face="微软雅黑"><?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></font>
选取节点
XPath 使用路径表达式在 XML 文档中选取节点。节点是通过沿着路径或者 step 来选取的。
下面列出了最有用的路径表达式:
实例
在下面的表格中,我们已列出了一些路径表达式以及表达式的结果:
选取根元素 bookstore。
注释:假如路径起始于正斜杠( / ),则此路径始终代表到某元素的绝对路径!
谓语(Predicates)
谓语用来查找某个特定的节点或者包含某个指定的值的节点。
谓语被嵌在方括号中。
实例
在下面的表格中,我们列出了带有谓语的一些路径表达式,以及表达式的结果:
选取未知节点
XPath 通配符可用来选取未知的 XML 元素。
在下面的表格中,我们列出了一些路径表达式,以及这些表达式的结果:
如图,假设我要抓取首页一个banner图,可以在chrome按下F12参考该元素的Xpath,
即该图片对应的Xpth为: //*[@id="loginWrap"]/div[4]/div[1]/div[1]/div/a[4]/img
解读:该图片位于ID= loginWrap下面的第4个div下的...的img标签内
为什么这里介绍Xpath,是因为我们网页分析是使用HtmlAgilityPack来解析,他可以把根据Xpath解析我们所需的元素。
比如我们调试确定一个文章页面的特定位置为标题,图片,作者,内容,链接的Xpath即可完全批量化且准确地解析以上信息
四、 使用HtmlAgilityPack解析网页内容
HttpTool类里封装了一个较多参数的HTTP GET操作,用于获取搜狗的页面:
因为搜狗本身是做搜索引擎的缘故,所以反爬虫是非常严厉的,因此HTTP GET的方法要注意携带很多参数,且不同页面要求不一样.一般地,要带上默认的
referer和host 然后请求头的UserAgent 要伪造,常用的useragent有
- <font face="微软雅黑">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",
- };</font>
自定义的GET 方法
- <font face="微软雅黑">/// <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 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;
- }</font>
前面关于Xpath废话太多,直接上一个案例,解析公众号页面:
- <font face="微软雅黑">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("https://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;
- }</font>
以上,说白了,解析就是Xpath调试,关键是看目标内容是是元素标签内容,还是标签属性,
如果是标签内容即形式为 <h>我是内容</h>
则: node.SelectSingleNode("div/div[2]/p[2]/label").InnerText.Trim();
如果要提取的目标内容是标签属性,如 <a href="http://www.360doc.com/im_target_url.htm" >点击链接</a>
则node.SelectSingleNode("div/div[@class='img-box']/a").GetAttributevalue("href", "")
五 、验证码处理以及文件缓存
这里需要着重的说一下,因为这个页面是属于微信的,所以反爬虫系统非常厉害!所以需要多次刷新容易产生要属于验证码的页面!
因此要向一个网址post验证码才可以解封
解封操作如下:
- <font face="微软雅黑">/// <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 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 == 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;
- }</font>
解释下:
先访问一个验证码产生页面,带17位时间戳
var timeStamp17 = (DateTime.UtcNow - Epoch).TotalMilliseconds.ToString("R"); //get timestamp with 17 bit
再向这个url query post你的验证码:
因此这里记得要启用如果启用了cookie,会通过FileCache类将cookie保存在缓存文件,下次请求如果开启cookie container的话就会带上此cookie
- <font face="微软雅黑">cookieCollection cc = Tools.LoadcookieFromCache();
- request.cookieContainer = new cookieContainer();
- request.cookieContainer.Add(cc);</font>
上面只是一部分,刚开始写的时候也没想到会有这么多坑,但是没办法,坑再多只能自己慢慢填了,比如OCR,第三方打码接入,多线程等等后期再实现。一个人的精力毕竟有限,相对满大街的Python爬虫,C#的爬虫性质的项目本来就不多,尽管代码写得非常粗糙,但是我选择了开放源码希望更多人参与
特别提示:本信息由相关用户自行提供,真实性未证实,仅供参考。请谨慎采用,风险自负。
0 条相关评论
相关最新动态
- 《乐一通大电影:地球爆炸之日》定档4月18日,华纳经典IP回归银幕
- 如何轻松开启vivo手机的NFC功能vivo手机有nfc功能吗「如何轻松开启vivo手机的NFC功能」
- 8月iPhone淡季,小米成功反超全球手机销量排行榜「8月iPhone淡季,小米成功反超」
- 格式工厂电脑版 V3.9.9 安卓版格式工厂手机版下载「格式工厂电脑版 V3.9.9 安卓版」
- 捕鱼大赛官方版 v5.2.2.0捕鱼手机版「捕鱼大赛官方版 v5.2.2.0」
- 在家也能赚!5个可靠的手机兼职副业推荐手机副业「在家也能赚!5个可靠的手机兼职副业推荐」
- 2025“乐龄·乐游”首批精品线路在沪集中发团
- PotPlayerv1.7.21632potplayer手机版下载「PotPlayerv1.7.21632」
推荐最新动态
点击排行
- 1001经典单机棋牌手机游戏,重温指尖上的竞技时光手机棋牌游戏「经典单机棋牌手机游戏,重温指尖上的竞技时光」
- 992“明前龙井”稀缺营销:一瓶茶饮料二手价竟翻倍
- 983adb 通过wifi连接手机adb连接手机「adb 通过wifi连接手机」
- 9748月iPhone淡季,小米成功反超全球手机销量排行榜「8月iPhone淡季,小米成功反超」
- 965华为畅享5手机怎么样 华为畅享5全面评测图解5寸手机「华为畅享5手机怎么样 华为畅享5全面评测图解」
- 966游戏改编电影《愤怒的小鸟3》官宣定档
- 967如何轻松开启vivo手机的NFC功能vivo手机有nfc功能吗「如何轻松开启vivo手机的NFC功能」
- 948华为Nova11SE耗电快怎么解决华为手机耗电快怎么解决「华为Nova11SE耗电快怎么解决」
- 949备忘录便签记事本手机备忘录「备忘录便签记事本」