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

一、背景

母亲喜欢听评书,跟着广播每天一集总觉得不过瘾,于是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<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)
{
    //点击一个有声读物,进入其详细窗口
    //待补充
}

本想一篇文章介绍完,发现内容还真不少(也可能是我写的啰嗦,见谅),于是改成上下篇吧,会尽快推出下一篇。

未完待续...

时间: 2024-10-09 06:02:47

抓取分析网页批量下载评书(上)之搜索有声小说的相关文章

Python抓取网页&amp;批量下载文件方法初探(正则表达式+BeautifulSoup) (转)

Python抓取网页&批量下载文件方法初探(正则表达式+BeautifulSoup) 最近两周都在学习Python抓取网页方法,任务是批量下载网站上的文件.对于一个刚刚入门python的人来说,在很多细节上都有需要注意的地方,以下就分享一下我在初学python过程中遇到的问题及解决方法. 一.用Python抓取网页 基本方法: [python] view plaincopyprint? import urllib2,urllib url = 'http://www.baidu.com' req 

python Beautiful Soup 抓取解析网页

Beautiful Soup is a Python library designed for quick turnaround projects like screen-scraping.总之就是一个解析xml和html之类的库,用着还算顺手. 官网地址:http://www.crummy.com/software/BeautifulSoup/ 下面来介绍下使用python和Beautiful Soup 抓取一个网页上的PM2.5数据. PM2.5 数据的网站:http://www.pm25.

Fiddler高级用法—Fiddler Script抓取app网页json数据并保存

FiddlerScript 环境搭建 官网下载: https://www.telerik.com/fiddler 安装步骤参照下面这篇文章(安装证书抓取https皆有详细步骤): https://www.cnblogs.com/liulinghua90/p/9109282.html 简单Fiddler Script 如下展示了Fiddler在客户端与服务端进行交互时的位置,在客户端发起http请求及接收服务端返回的数据时都可截取交互的数据.那么在Fiddler中我们就可以抓取所有http请求的数

使用scrapy-selenium, chrome-headless抓取动态网页

????在使用scrapy抓取网页时, 如果遇到使用js动态渲染的页面, 将无法提取到在浏览器中看到的内容. 针对这个问题scrapy官方给出的方案是scrapy-selenium, 这是一个把selenium集成到scrapy的开源项目, 它使用selenium抓取已经渲染好(js代码已经执行完成)的动态网页. ????事实上selenium自己也没有渲染动态网页的能力,它还是得依赖浏览器, 用浏览器作为动态网页的渲染引擎. 目前主流的浏览器都能以headless模式运行, 即没有图形界面只有

Java写的抓取任意网页中email地址的小程序

/* * 从网页中抓取邮箱地址 * 正则表达式:java.util.regex.Pattern * 1.定义好邮箱的正则表达式 * 2.对正则表达式预编译 * 3.对正则和网页中的邮箱格式进行匹配 * 4.找到匹配结果 * 5.通过网络程序,打通机器和互联网的一个网站的连接 */ import java.net.*; import java.util.regex.*; import java.io.*; public class EmailAddressFetch { public static

scrapy和selenium结合抓取动态网页

1.安装python (我用的是2.7版本的) 2.安装scrapy:   详情请参考 http://blog.csdn.net/wukaibo1986/article/details/8167590 (提示,能下载源码安装的就避免用pip install **) 安装过程中遇到python扩展问题”unable to find vcvarsall.bat“的解决办法: http://blog.csdn.net/ren911/article/details/6448696 3.安装seleniu

&lt;&lt;&lt; JS实现网页批量下载文件,支持PC/手机

//把下载链接放入集合里 var downloadData = new Array{"http://www.empli.com/data1.apk","http://www.empli.com/data1.apk","http://www.empli.com/data1.apk","http://www.empli.com/data1.apk"}; var downloadNum=0;//方法执行次数 circularWind

queryList 一次抓取多个网页内容的方法--目前只有用循环 替换页码或者给出url循环进行 queryList没有像python一样的yied迭代方法 queryList 实现多个实例抓取不同网页的内容,两个实例数据互不干扰

注意: 目前只有用循环 替换页码或者给出url循环进行   queryList没有像python一样的yied迭代方法  queryList 实现多个实例抓取不同网页的内容,两个实例数据互不干扰 新技能获取: Medoo(轻量级php数据库框架:https://medoo.lvtao.net/) 实现循环采集多个页面数据: 关键代码  for ($i = 1; $i < 21; $i++) { echo "正在爬取第{$i}页\n"; $url = "http://bl

实例:使用puppeteer headless方式抓取JS网页

puppeteer google chrome团队出品的puppeteer 是依赖nodejs和chromium的自动化测试库,它的最大优点就是可以处理网页中的动态内容,如JavaScript,能够更好的模拟用户. 有些网站的反爬虫手段是将部分内容隐藏于某些javascript/ajax请求中,致使直接获取a标签的方式不奏效.甚至有些网站会设置隐藏元素"陷阱",对用户不可见,脚本触发则认为是机器.这种情况下,puppeteer的优势就凸显出来了. 它可实现如下功能: 生成页面的屏幕截图