可配置的屏幕抓取,用于离线使用






3.60/5 (5投票s)
对屏幕抓取、XML 数据源以及 Web 服务的一些探索。
引言
本文及包含的完全可运行代码,详细说明了我如何构建一个屏幕抓取实用程序和一个前端,用于使用您的 .NET CF 设备搜索新获取的数据。我的示例从一些知名餐厅的网页中提取数据(位于用户提供的邮政编码附近的实际地址),并将数据插入到由 XML 文件组成的数据库中。我还包含了“插件”功能,可以通过 Web 服务提供额外的餐厅定义(用于在给定网站上查找相关数据的模式)。
在您像其他人一样放弃此应用程序之前,请注意,此应用程序仅是技术上具有创新性的概念证明,以一种复古的方式。下面的代码是有限的 - 请将其下载到您的 Pocket PC(放入Program Folders\RestaurantScraper)中,并进行试用。当然,源代码也已包含并附有文档。
很可能,您出于好奇而阅读这篇文章,嘴里嘟囔着“他做了什么?为什么?!?”。我必须诚实地说,在过去的一周里,我多次问过自己这个问题。让如此复杂的平台(.NET CF)负责从 HTML 中提取数据,这与我们在这个技术蓬勃发展的时代所学到的一切都背道而驰。但我发现,即使有如此多的技术可用,许多公司尚未拥抱 Web 服务以供大众消费,无论是有意还是无知。将来可能会有一个一切都可用的时候,可以说是数字乌托邦,但那个时候还没有到来。在此之前,获取宝贵的数据(香料?)可能需要一些非常规的技术,而且很可能完全无视那些烦人的“使用条款”协议。
请注意:尽管我的程序只执行 HTTP 请求,与您的浏览器发出的请求相同,但屏幕抓取过程和收集数据的存储在我的使用网站的使用条款中是不被允许的。对于您的行为,我概不负责,也不对您使用我的程序可能造成的任何损害负责。
背景
我一直认为屏幕抓取的过程几乎和理查·基尔一样丑陋。就像《落跑新娘》一样,应该不惜一切代价避免。对于那些从未听说过它的人(屏幕抓取,不是《落跑新娘》),屏幕抓取是在文本池中查找模式,并提取其中数据的过程。这真的可以比作大海捞针(但必须说,这是一个组织良好的草垛)。例如,尝试在源 HTML 文档中查找此文本(对于我们所有比利·盖茨的追随者来说,选择“查看 - 源文件”)。很可能,它在几百行之后,尽管前面只有几段。您会发现这一段夹在许多用于格式化的<p>和<td>标签之间。屏幕抓取不流行的主要原因之一是,如果网页格式发生变化,很可能会破坏应用程序正确解析它的能力。这是一个无法解决的重大问题。
在编程层面,获取 HTML 文档中的数据需要遍历这些数百或数千行的 HTML 标记,查找已定义的模式。这就是我所做的——使用可以遍历 HTML 文档的逻辑,查找模式,并将结果数据本地存储,以便在离线时进行引用。此应用程序主要展示了如何使用 XML 文件作为数据源,使一个思维僵化的独立应用程序功能齐全且功能强大。
使用代码
应用程序的核心 resides 在 exctract.cs 类中。几乎所有的应用程序逻辑都在该类中。应用程序的核心,网页请求,如下所示。显然还有更多代码 - 我鼓励您下载并亲自查看。
private static void makeRequest(string url,
DataSet ds, DataRow settings,string companyName)
{
//Create the request using the supplied URL
//** NOTE ** The only input that is supplied by the user
//is which company and what zip code.
// The definition file for the website provides
// the url prior to the zipcode and after the zip code.
HttpWebRequest hr = (HttpWebRequest)WebRequest.Create(url);
HttpWebResponse response = (HttpWebResponse)hr.GetResponse ();
Stream receiveStream = response.GetResponseStream ();
// Pipes the stream to a higher level stream reader
// with the required encoding format.
StreamReader readStream = new StreamReader (receiveStream, Encoding.UTF8);
string fullPage = readStream.ReadToEnd();
response.Close ();
readStream.Close ();
//BEGIN gets locations of applicable data
string endIndicator = settings["endIndicator"].ToString();
string startIndicator =
settings[@"startIndicator"].ToString().Replace("NEWLINE","\n");
int begin = fullPage.IndexOf(startIndicator,startIndex);
//END gets locations of applicable data
非常直接。在我看来,真正酷的概念在于 endIndicator
和 startIndicator
行。您会注意到它们的值是根据 settings
DataRow
的输入设置的。这个 DataRow
直接来自定义(或配置)文件,为网站量身定制(见下一节 - mcdonalds.xml)。
这是我使用 XSD 和 XML 作为数据源的一个示例。
DataSet ds = new DataSet("addresses");
ds.ReadXmlSchema(schemaPath + @"\addresses.xsd");
ds.ReadXml(dataPath+@"\locations.xml");
如果您要将此代码安装到您的 PDA 或 PDA 模拟器上,请在 \Program Files 文件夹中创建一个名为 RestaurantScraper 的目录,并将所有编译后的文件提取到该文件夹。
运行应用程序时,系统会要求您确认许可协议(参见上面关于网站“使用条款”页面的说明)。接受后,点击“文件 - 数据维护”。假设您的 PDA 具有网络连接(通过 ActiveSync 或无线网络),在表单顶部输入邮政编码,然后按“下载”。几秒钟后,记录数量应会跳到 20 条左右,具体取决于该邮政编码区域内有多少家麦当劳。我相信这只包含美国的麦当劳。所以,如果您不知道任何邮政编码,可以尝试 90210。按 OK 关闭表单,然后您就可以针对这些数据进行搜索了。回到“数据维护”表单,您可以清除已存储的位置。此外,您还可以下载新的定义(我有一个来自冰淇淋店 Baskin Robbins 的可下载定义),方法是按“下载新定义”按钮。这将调用一个 Web 服务,应用程序将自动下载并安装它。
XML 和 XSD 如何用于定义站点
我的代码附带了麦当劳网站(由 vicinity.com 维护)的商店定位器站点的站点定义。站点定义(mcdonalds.xml)包含以下元素(由 XML 模式 definition.xsd 强制执行)
startIndicator
- 一个可识别的字符串,紧跟在 HTML 页面中的相关数据(麦当劳的实际地址)之后。endIndicator
- 一个标记相关数据结束的字符串(请注意,数据需要清理 - 这只是标识了数据的结束)。contentSeperator
- 一个字符串,通常是换行符,用于区分街道地址和 [城市、州、邮政编码、国家] 文本。baseURL
- 目前未使用。contentURLBegin
- 如果用户进行了真实的麦当劳位置搜索,这将是地址栏中邮政编码前面的文本。contentURLEnd
- 如果用户进行了真实的麦当劳位置搜索,这将是地址栏中邮政编码后面的文本。
已安装站点定义的列表保存在单独的 XML 文件 definitions.xml 中(由应用程序维护,并由 definitions.xsd 强制执行)。顺便说一句,Web 服务(未包含在附加代码中)依赖于相同的模式,并使用此模式布局返回一个 DataSet
。
displayName
- 公司名称(例如,麦当劳)。fileName
- [definition].xml(例如,mcdonalds.xml。它与上面定义的相同)。
已下载的实际位置列表存储在 locations.xml 中(由 addresses.xsd 强制执行)。此文件将在首次使用时创建。值得注意的字段有:
unid
- 一个字符串,是主键。它基本上是地址的组合。在将来的查找中,如果此键已存在,则不会创建具有相同值的另一个位置记录。street
、city
、state
和zip
字段 - 不言而喻。companyName
- 是的,这本可以与 definitions.xml 文档建立关联,但实际上并没有。
关注点
我的主要目标是使其成为一个即插即用的应用程序 - 使其他网站能够用最少的精力进行配置和安装。硬编码单个站点的起始点和结束点以便进行屏幕抓取并非极其困难,但构建一个允许无限数量站点被抓取的架构对我来说非常有吸引力(不像理查德·基尔)。我必须承认,此应用程序的创建是为了满足至少两个站点的需求。它只能处理 URL 中包含邮政编码的站点。它无法获取电话号码或其他信息(例如到该位置地图的链接)。它可以做到,但这更多地是一个概念证明。
让我说,我最令人沮丧的障碍在于定义文件系统上 XML 文件和 XSD(XML Schema)文件的位置差异。下面的示例直接取自代码(并略作格式化),指向同一位置。但不知何故,schema 变量需要替换空格为 %20,而 XML 路径不能有 %20。我没有在这上面浪费太多时间,但一旦我找到了解决方案,我只能翻白眼。
public string schemaPath =@"\Program%20Files\RestaurantScraper";
ds.ReadXmlSchema(schemaPath + @"\addresses.xsd");
public string dataPath = @"\Program Files\RestaurantScraper";
ds.ReadXml(dataPath+@"\locations.xml");
我对在这个应用程序中使用 Web 服务感到非常满意。虽然它不是整个应用程序的整体组成部分,但该应用程序能够调用一个 Web 服务(目前在我个人网站上),该服务将返回一个包含定义(例如,mcdonalds.xml 和 baskinrobbins.xml)的 DataSet
。Pocket PC 应用程序将此 DataSet
与本地定义列表(definitions.xml)进行比较,如果有缺失的,则下载它们(也在我的网站上),并立即使其可用。我认为这非常巧妙。
玩得开心。感谢您的阅读 - 欢迎评论和评分。