一、背景
母亲喜欢听评书,跟着广播每天一集总觉得不过瘾,于是2010年给她买了一个带内存,能播放MP3的音箱,从此给她找评书便成了我的责任和义务。
一开始开始还好,单先生说的书多,找起来不困难, 但随着听的越多,加上听惯了单先生的,其他人的母亲都不喜欢,即便单先生的,类似白眉大侠、童林传等武侠类的她也不爱听(本人也不是很喜欢,规律都差不多,自己被欺负了,找兄弟,再不行找师傅,还不行,找师祖,总之一句话你等着,我叫人去),后来实在找不到了,也慢慢的试着听孙一,张少佐等其他人的了。
电驴被封后,而能打包下载mp3的网站越来越少,想找点评书着实让人挠头。
随着智能机的普及,母亲已经使用手机听评书了,所以本文仅供学习交流,请勿用于商业及非法用途。
二、所需技能及工具
想要实现批量下载需要三样利器。
1、Visual Studio,传说中的编程界的九阳神功,我现在一般是2010和2015交替使用。
2、正则表达式,童子功,打好基础开发速度事半功倍。
3、IE10以上、Edge 、Chrome等浏览器,相当于慕容世家的绝学斗转星移。
三、要实现的功能
1、搜索评书或其他有声读物。
2、下载评书。
四、实战开始
软件实现搜索功能很简单,大体上三步即可
1)、模拟浏览器向网站提交查询 ,获取返回的结果 。
2)、分析结果,写出对应的正则表达式,生成结果的记录集 。
3)、将记录集呈现在窗体上。
1、首先让我们看看tingChina的查询页面什么样?
2、选中评书,选择在演播中搜索,输入单田芳,点击搜索资源,结果页面如下
咱们分析一下链接地址,mainlei从名称看,应该是指有声小说、评书、相声、戏曲、儿歌、人文、笑话,查询界面第一行的搜索分类。我选的是评书,而mainlei=1,那有以此类推一下,有声读物对应的是0,相声对应的是2,具体的试一下就能简单的验证。
而lei应该就是查询界面中的那个下拉框,一共有标题、演播、作者三个选项,而我选的是演播,而lei=1,同样以此类推,标题应该就是0,作者则是2。
3、分析完链接地址,接下来咱们分析页面的代码,笔者习惯使用IE浏览器,所以直接在查询结果页面按F12,然后按图中的提示操作即可。
按照图中的提示,能提取一段html代码,当然,如果你对html非常熟悉,直接查看源代码找到这段代码也行。
这时,大家要注意三个问题。
1、有的li元素包含style="background-color:#F3F3F3;"这段代码,有的不包含。
2、有的a标签包含style="color:blue;"这段代码,有的则不含。
3、根据mainlei的不同,结果中链接地址也有些区别,比如评书就是<a href="pingshu/disp_1209.htm">,而有声读物则是<a href="yousheng/disp_27623.htm">,注意粗体的部分。
这种情况正则表达式怎么写呢,对于前两种情况,这里我用了一种简单粗暴的方法,就是把这两段内容统一替换掉,这样就能简化正则表达式了。
最终的正则表达式:<li><a href=""(?<mainlei>[\w]*)/disp_(?<Number>[\d]*).htm""\s*>(?<Title>[\s\S]{1,20}?)</a>\([\d]*\)</li>
4、软件搜索的最终界面如下:
5、主要代码如下:
C# Code
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 |
private void frmSearch_Load(object sender, EventArgs e)
{ List<DictionaryEntry> list = new List<DictionaryEntry>(); list.Add(new DictionaryEntry("0","标题")); list.Add(new DictionaryEntry("1","演播")); list.Add(new DictionaryEntry("2","作者")); this.cmb_lei.DisplayMember = "Value"; this.cmb_lei.ValueMember = "Key"; this.cmb_lei.DataSource = list; //this.cmb_lei.SelectedValue = "1"; } //http异步请求的回调函数 public void ResponseCallBack(IAsyncResult result) { string Html = ""; HttpWebRequest req = (HttpWebRequest)result.AsyncState; try { using (HttpWebResponse response = (HttpWebResponse)req.EndGetResponse(result)) { Stream resStream = response.GetResponseStream(); StreamReader sr = new StreamReader(resStream, Encoding.GetEncoding("GB2312")); Html = sr.ReadToEnd(); } } catch(Exception ex) { if (IsDisposed || !this.IsHandleCreated) return; this.Invoke(new Action(() => { MessageBox.Show("查询时出现异常,原因:" + ex.Message); })); return; } //替换掉干扰代码 Html = Html.Replace(@" style=""background-color:#F3F3F3;""", "").Replace(@" style=""color:blue;""", ""); //动态生成Label的字体 Font font = new Font("微软雅黑", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); //正则表达式分析网页,查找查询结果 MatchCollection ms = Regex.Matches(Html, @"<li><a href=""(?<mainlei>[\w]*)/disp_(?<Number>[\d]*).htm""\s*>(?<Title>[\s\S]{1,20}?)</a>\([\d]*\)</li>", RegexOptions.IgnoreCase | RegexOptions.Multiline); if (ms.Count > 0) { //最大宽度 int MaxWidth = 0; Dictionary<string, string> list = new Dictionary<string, string>(); foreach (Match m in ms) { list.Add(string.Format("http://www.tingchina.com/{0}/disp_{1}.htm", m.Groups["mainlei"].Value, m.Groups["Number"].Value), m.Groups["Title"].Value); //获取字体的大小,找到最宽的那个条记录 Size size = TextRenderer.MeasureText(m.Groups["Title"].Value, font); if (size.Width > MaxWidth) { MaxWidth = size.Width; } } if (IsDisposed || !this.IsHandleCreated) return; this.Invoke(new Action(() => { //对结果进行排序 Dictionary<string, string> listAsc = list.OrderBy(o => o.Value).ToDictionary(o => o.Key, p => p.Value); //循环动态生成查询结果. foreach (KeyValuePair<string, string> kvp in listAsc) { Label lbl = new Label(); lbl.Text = kvp.Value; lbl.Tag = kvp.Key; lbl.Cursor = System.Windows.Forms.Cursors.Hand; lbl.Margin = new System.Windows.Forms.Padding(5, 0, 5, 10); lbl.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(192)))), ((int)(((byte)(0))))); lbl.Font = font; lbl.Width = MaxWidth; lbl.Click += new EventHandler(lbl_Click); this.fpnl_Content.Controls.Add(lbl); } })); } else { if (IsDisposed || !this.IsHandleCreated) return; this.Invoke(new Action(() => { MessageBox.Show("没有查找到数据,请更换关键词。"); })); } } private void btn_Search_Click(object sender, EventArgs e) { if (this.txt_key.Text.Trim() == "") { MessageBox.Show("请输入查询关键字."); return; } //循环获得分类编号 string mainlei = "0"; foreach (Control c in this.pnl_Search.Controls) { if (c.GetType().Equals(typeof(RadioButton)) && ((RadioButton)c).Checked) { mainlei = c.Name.Replace("rdo_mainlei", ""); break; } } //发送异步请求,根据关键字查询 string Url = string.Format("http://www.tingchina.com/search1.asp?mainlei={0}&lei={1}&keyword={2}", mainlei, this.cmb_lei.SelectedValue, HttpUtility.UrlEncode(this.txt_key.Text.Trim(),Encoding.GetEncoding("GB2312"))); HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(Url); request.Method = "GET"; request.BeginGetResponse(new AsyncCallback(ResponseCallBack), request); this.fpnl_Content.Controls.Clear(); } private void lbl_Click(object sender, EventArgs e) { //点击一个有声读物,进入其详细窗口 //待补充 } |
本想一篇文章介绍完,发现内容还真不少(也可能是我写的啰嗦,见谅),于是改成三篇吧,会尽快推出下一篇。
未完待续...