u如何设置url参数不自动转码_No.8 如何在2019年12月跟踪一本书

本文介绍如何利用C#在.NET框架下编写针对深圳图书馆的爬虫程序,通过HTTP请求GET方法获取图书信息,并解析HTML内容来跟踪书籍状态。通过设置URL参数和正则表达式匹配,实现对特定图书的检索和信息提取。
摘要由CSDN通过智能技术生成

如何在

深圳图书馆

跟踪一本书

08

本文成于去年十二月,因写得太差不好意思发。现在二十多年过去了,活鱼格勒发了不少写得比这篇还要差得多的文,所以不如把这篇也发了吧。

    既然我在图书馆失去了尤达大师,根据一些定理,易证我可以在图书馆把大师找回来。不仅要找回来,而且还要通知我,大师有没有被别人借走。

    本文将以C#在.NET框架下实现针对深圳图书馆的爬虫程序,并加入提醒机制。像上一篇推送一样,在开始实现前我们需要讨论一些有关的问题。

293a15f1e06592a4617f24c1db9aa710.gif

 HTTP请求

请求是一个很直观的过程:客户向服务器发送一个请求,服务器送回它的响应。发送请求的单位是报文,不同的网络协议要求不同格式的报文,这里不展开讨论。下面是我们将使用的HTTP协议的报文格式:

32f6527b67e9f222d68f6a2b5b600f9b.png

URL:接收请求的地址。一般来说浏览器上部地址框中展示的就是指向当前网页的URL。

请求方法:HTTP有六种请求方法,常用的是GET和POST。GET方法将请求的内容添加在URL后,以‘?’字符与URL主体分隔开来,因此使用GET方法的HTTP报文往往没有上图的“请求数据”。POST方法则将请求内容存放在报文末端的请求数据中。

由于GET格式的请求数据是直接包含在URL内的,而URL又必须按照相关协议规定以明文展示,所以可以很方便地分析GET请求。比如,百度搜索“晒布路”,实际请求的URL为:

40c8f9da3aac98c9be72fe60ddf35e8b.png

其中的……wd=%E6%99%92%E5%B8%83%E8%B7%AF是按照UTF-8编码转码“晒布路”三个中文字符得到的。这里不展开讨论字符编码和百分号编码格式,大部分浏览器甚至库函数会自动完成这种编码。

293a15f1e06592a4617f24c1db9aa710.gif

用C#实现HTTP请求

    众所周知,.NET框架下有大量封装良好的库函数,相比于C/C++的关注底层实现,C#的关注于上层功能使人感到极度的愉悦。

    为了实现GET方式的HTTP请求,我们使用HttpWebRequestHttpWebResponse类,这两个类位于System.Net命名空间下。

---HttpWebRequest---

这个类支持大量的HTTP报文属性。在本例中我们只需要简单地提交一个GET请求,因此这里不展开讨论这些属性。

创建一个HttpWebRequest类:

HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(webpath);

构造函数的参数即为请求的URL。

之后,设置这个实例的请求类型为GET:

request.Headers.Add("Accept-Encoding", "gzip, deflate");

---HttpWebResponse---

调用以上实例的GetResponse(),即获取响应函数,返回的是一个HttpWebResponse类,此类型保存响应的具体信息。在本例中,服务器响应的应是.html格式的网页文件,由于我们想要的信息“货真价实”地显示在了网页上,所以这些信息一定可以在这些网页文件中直接获取

获取请求的响应:

HttpWebResponse response = (HttpWebResponse)request.GetResponse();

为了获取完整的响应数据,这里新建一个数据流。流的概念不在本文讨论的范围内,可以简单地把这一操作理解为使储存在响应实例中的数据“流”向目标。

为了愉快地面对对象编程,我们把上面几个操作封装到一个新类型中:

namespace PieskiLib{    class WebRequest    {        internal string webpath;        internal WebRequest(string webpath)        {            this.webpath = webpath;        }        internal string Download()        {            HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(webpath);            request.Timeout = 36000;            request.Method = "GET";            request.UserAgent = "Mozilla/4.0";            request.Headers.Add("Accept-Encoding", "gzip, deflate");            HttpWebResponse response = (HttpWebResponse)request.GetResponse();            Stream dld_stream = response.GetResponseStream();            StreamReader reader = new StreamReader(dld_stream, Encoding.UTF8);            string html = reader.ReadToEnd();            dld_stream.Close();            dld_stream.Dispose();            reader.Close();            reader.Dispose();            return html;        }    }}

这个类型的成员函数Download()返回由构造函数确定的请求得到的全部响应数据字符串。在本例中,这个字符串包含的是html文件的全部代码文本。

293a15f1e06592a4617f24c1db9aa710.gif

获取图书馆数据

深图的书,多。我们找的是尤达大师,因此我们进入图书检索主页,在“书名”后输入“尤达大师”。

00ffebf945462e4191ad62cafc314274.png

(https://www.szlib.org.cn/Search/search.jsp)

得到的结果只有一本,如下:

db0e544f54b11f517f8d3fed4f7c3211.png

这个标题让我产生了一些疑虑。

5688d9721f29f12e311c0acf5d38a15a.png baa22068bafd1508ced96305c5709d6c.png 12d76caa333eadf5dd660cfe342d5224.png

这和我想的大师有一定差距。不过这个例子还是可以展现深圳图书馆的图书检索框架:在首页输入书名/作者/出版社等书籍特征,发送请求,得到一个符合特征的图书列表。这个列表里的每一项包含了该书的详细信息及馆藏资源。

现在,摆在我们和自动获取数据之间的只有一个问题:这个系统的请求方式是GET吗?

853159a0267a37add0e7e5d18d919ff0.png

这是搜索“尤达大师”后Chrome浏览器地址栏中的内容,可见确实是GET无疑。注意:这里浏览器自动把百分号+编码字节转码成中文了。

293a15f1e06592a4617f24c1db9aa710.gif

解析图书馆数据

     在前三节中,我们介绍了如何向深图服务器发送一个HTTP请求,以及深图检索图书的逻辑。在这一节中我们将解析服务器响应,从中获取目的信息。

如前文所述,在图书检索系统主页按书名搜索“尤达大师”,实际发送的URL为:

e1614292c90252fe3bc436e80991408a.png

    乍看上去令人头大。实则:令人头大。

    但是,仔细看来,请求信息中只有v_value字段后有%E5%B0%A4……等一段字符编码、v_index被设置为“title”。v_startpubyearv_publisherv_author等字段则全部留空。可以推测v_index定义搜索模式,v_author等则是作者、出版社一类的限制信息。据此,可以得到生成请求信息的格式

在请求中可以加入v_library字段指定查找某个特定图书馆的馆藏。经过多次试验,已知的图书馆编号有:

  • 深圳图书馆(本馆):044005

  • 南山图书馆:044006

  • 宝安图书馆:044007

  • 福田图书馆:044008

以上述URL发送请求后,得到的相应全文有1,118行。绝大部分是我们不关心的页面格式、网页逻辑等信息。我们感兴趣的图书信息格式如下:

c721157f690622ca79b8dfa80d106e56.png

可见每个搜索结果的主要信息在一个

  • 标签和
标签中存储。为了匹配这些信息,我们使用 正则表达式对其进行分析。遗憾的是,正则表达式有一个复杂的系统,现在无法展开讨论。在这里我们提供匹配以上信息的表达式:
  • [\S\s]*?

为了在VisualStudio编译器中使用,需要对其中的特殊字符进行转义:

  • [\\S\\s]*?

以上的表达式匹配的是“booklist”,即书单。若要匹配其中的一本书,需要进一步使用下面的表达式(已转义特殊字符):

[\\s\\S]*?

而要匹配每本书的更多信息,又要用到更多的正则表达式:

匹配图书详细信息链接:href=\\\"[\\s\\S]*?\\\"

匹配图书标题:title\\s=\\s\\\"[\\S\\s]*?\\\"

匹配作者:作 者:[\\s\\S]*?<

匹配出版年 :出版年:[\\s\\S]*?<

匹配出版社:出版者:[\\s\\S]*?<

(注意,以上表达式(以斜体区分)中的中文不可删去)

上面的工作只是为了获取图书的基本信息。下面还要对匹配到的详细信息链接进行请求,获取图书在馆情况等资源信息。

根据上面匹配到的详细信息链接,《星球大战神秘事件簿》对应的页面链接为:https://www.szlib.org.cn/Search/searchdetail.jsp?v_tablearray=bibliosm,serbibm,apabibibm,mmbibm,&v_recno=4713184&v_curtable=bibliosm&site=null

细心的读者会发现,这一URL已经包含了GET请求信息,我们不需要对其进行任何修改,只需以此URL发起请求,得到包含详细信息页面HTML文件的服务器响应。本例对应的html文档有829行,大部分依然对我们无用。我们要做的是使用下面给出的正则表达式匹配目标信息:

转义后:

为了帮助理解匹配过程,以下是使用sublime文本编辑器进行匹配的结果:

4f26325485ed77c3f05043756864e125.gif

可见,每个信息单元(如条形码、索书码、藏书地点、借出信息)都按顺序存储

标签内。我们只要对这个标签匹配一次,然后在得到的结果列表中筛选各个信息。

如果信息量太大开始乱了,没有关系。下面是根据上述方法建构的图书资源模型:

09937232d0c2a539e4ec3bf517cd0add.png

其中Book类模拟一种图书,对应在检索主页搜索得到的图书列表中的一项;BookResource类模拟某种图书中的一本,对应详细信息页面展示的图书资源中的一项。要获取《星球大战神秘事件簿》中一本的馆藏位置,需要四步操作:

  1. 获取与“星球大战神秘事件簿”(事实证明也就是与“尤达大师”)相关的图书列表

  2. 使用正则表达式匹配在列表中选取一项(本例中只有一项),创建Book类实例

  3. 使用正则表达式获得Book类实例指向的详细页面URL

  4. 使用正则表达式在详细页面中获得馆藏信息等

至此,解析完毕。

293a15f1e06592a4617f24c1db9aa710.gif

实现查找

     由于代码总行数在300行上下,这里不方便直接列出全部代码,仅对部分内容进行解释。要获取完整代码,请访问:

https://github.com/Pieski/PieskiLib(Github页面)

---Program.SearchByName---

根据书名发送URL,并获得图书清单

static void SearchByName(string name){    string path = "https://www.szlib.org.cn/Search/searchshow.jsp?" +        "v_tablearray=bibliosm%2Cserbibm%2Capabibibm%2Cmmbibm%2C&" +        "v_index=title&v_value=" + name + "+&cirtype=&" +        "v_startpubyear=&v_endpubyear=&v_publisher=&v_author=&" +        "sortfield=ptitle&sorttype=desc";    if (library != null)        path += "&library=" + library;    WebRequest request = new WebRequest(path);    string htmlinfo = request.Download();    Match booklist = Regex.Match(htmlinfo,        "
[\\S\\s]*?");    MatchCollection books = Regex.Matches(booklist.Value,        "[\\s\\S]*?");    List result = new List(); foreach (Match match in books)    {        Book book = new Book(match.Value);        book.GetResources(); result.Add(book);    }    ShowResult(result);}

函数参数为书名。path是请求的URL。如果用户此前设置了图书馆编号,则在URL中加入图书馆字段。

Match是正则匹配类型,MatchCollection是匹配结果类型。

---Book.Book---

Book类的构造函数,根据与此类型有关的html文本段匹配出标题、作者等信息

internal Book(string html){    string ori_link = Regex.Match(html, "href=\\\"[\\s\\S]*?\\\"").Value;    string ori_tittle = Regex.Match(html, "title\\s=\\s\\\"[\\S\\s]*?\\\"").Value;    string ori_author = Regex.Match(html, "作 者:[\\s\\S]*?).Value;    string ori_date = Regex.Match(html, "出版年:[\\s\\S]*?).Value;    string ori_publisher = Regex.Match(html, "出版者:[\\s\\S]*?).Value;    link = "https://www.szlib.org.cn/Search/" + ori_link.Split('"')[1];    title = ori_tittle.Split('"')[1];    author = ori_author.Replace("作 者:", "").Replace(","");    date = ori_date.Replace("出版年:","").Replace(", "");    publisher = ori_publisher.Replace("出版者:", "").Replace(", "");}

函数参数为与此类型有关的html文本段,是SearchByName函数初步匹配的结果。

---BookResource.BookResource---

BookResouce类的构造函数,根据与此类型有关的html文本段匹配出条形编码号、索书码、借还情况等

internal BookResource(string html){    MatchCollection tdinfos = Regex.Matches(html, "
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值