相信的勇气

一个执着的编程爱好者,专注于让学到的新知识落地。
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

抓取分析网页批量下载评书(1)之搜索有声小说

Posted on 2016-03-14 11:55  相信的勇气  阅读(4516)  评论(3编辑  收藏  举报
 
一、背景
      母亲喜欢听评书,跟着广播每天一集总觉得不过瘾,于是2010年给她买了一个带内存,能播放MP3的音箱,从此给她找评书便成了我的责任和义务。

      一开始开始还好,单先生说的书多,找起来不困难, 但随着听的越多,加上听惯了单先生的,其他人的母亲都不喜欢,即便单先生的,类似白眉大侠、童林传等武侠类的她也不爱听(本人也不是很喜欢,规律都差不多,自己被欺负了,找兄弟,再不行找师傅,还不行,找师祖,总之一句话你等着,我叫人去),后来实在找不到了,也慢慢的试着听孙一,张少佐等其他人的了。

      电驴被封后,而能打包下载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<
stringstring> list = new Dictionary<stringstring>();
        
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<stringstring> listAsc = list.OrderBy(o => o.Value).ToDictionary(o => o.Key, p => p.Value);

            
//循环动态生成查询结果.
            foreach (KeyValuePair<stringstring> 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(50510);
                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)
{
    
//点击一个有声读物,进入其详细窗口
    //待补充
}

本想一篇文章介绍完,发现内容还真不少(也可能是我写的啰嗦,见谅),于是改成三篇吧,会尽快推出下一篇。
未完待续...
 
本文为博主原创文章,欢迎转载分享但请注明出处及链接,否则将其追究法律责!
勤奋的男人和爱笑的女人,运气一般都不会太差。