URL 抓取器






4.75/5 (8投票s)
本文描述了一个 ASP.NET Web Pages 项目,用于抓取与某些词汇高度匹配的页面的 URL。
引言
在这篇文章中,我描述了一个 Web 应用程序,它对于收集与一组给定单词匹配的 Web 页面 URL 很有用。该应用程序可以从 这里运行。(注意:已部署的应用程序允许每个用户每天最多进行五次查询。)
该应用程序对于查找包含大量与所搜索主题相关的 URL 的页面很有用。例如,使用查询“英语播客 mp3”,该应用程序将找到包含许多用于学习英语的 MP3 播客链接的页面。
该应用程序演示了一些实用概念,包括 Web 爬取、泛型集合以及使用 WebRequest (在 System.Net 中) 和 WebGrid (在 System.Web.Helpers 中) 类。该应用程序是使用 Microsoft 的 WebMatrix(用于开发的 IDE)使用单个“.cshtml”文件开发基于 Razor 的应用程序的良好示例,而无需为模型、视图和控制器使用几个单独的文件。
应用程序的工作原理
该应用程序是一个在 MS Razor 中开发的单页面应用程序 (SPA)。它由一个包含 HTML 和处理逻辑的 Razor 视图页面组成。
@{
int StartTimer = 0;
string ProgressInfo = "";
WebGrid grid = null;
// Server.ScriptTimeout = 30;
int MaxRecords = 20; // Default value for MaxRecords
string searchWords = "English podcast mp3"; // Default value for searchWords
if (Request["hvar"] =="submit")
{
MaxRecords = int.Parse(Request["MaxRecords"]);
if (MaxRecords > 60)
{ ProgressInfo = "Maximum Records cannot exceed 60.";
goto finish;
}
StartTimer = 1;
Grabber grabber = new Grabber();
grabber.Session = Session;
var urlTable = (HashSet<RowSchema>) Session["urlTable"];
if (urlTable==null)
{ urlTable = new HashSet<RowSchema>();
Session["urlTable"] = urlTable;
}
else if (Request["refresh"] =="0")
{ urlTable.Clear(); }
searchWords = Request["searchWords"];
bool status = grabber.Search(searchWords, MaxRecords, dt);
grid = new WebGrid(source:urlTable, rowsPerPage:100);
grid.SortDirection = SortDirection.Descending;
grid.SortColumn = "Count";
int visitedCount = urlTable.Where(p => p.Visited).Count();
ProgressInfo = "Visited count = " + visitedCount + "; Page will refresh after 15 seconds ...";
if (status)
{ StartTimer=0; // Used to disable refresh timer on client side
ProgressInfo = "Finished";
}
}
finish:
}
}
// some more stuff here
:
<form action="" method="post" >
<input name="hvar" type="hidden" value="submit" />
<input id="refresh" name="refresh" type="hidden" value="0" />
<label>Maximum Records</label><input type="text" name="MaxRecords" value="@MaxRecords" size="4" />
<label>Search Word(s)</label><input type="text" name="searchWords" value="@searchWords" size="35" />
<input type="submit" value="Search" onclick="submitForm()" />
<input type="button" value="Stop" onclick="DoStop()" />
</form>
<div style="margin-left:10px" >
<p id="status" >@ProgressInfo</p>
<!-- render grid here -->
@if (grid!=null)
{ @grid.GetHtml() }
</div>
前面的列表显示了表单的 HTML 和每次访问页面时执行的服务器端程序代码。
在代码中,行 Grabber grabber = new Grabber();
创建一个“Grabber”对象。调用 grabber.Search(searchWords, MaxRecords, urlTable);
爬取 Web 并用与 searchWords 参数指定的单词高度相关的 URL 填充一个集合 (urlTable 参数)。
行 grid = new WebGrid(source:urlTable, rowsPerPage:100);
将 urlTable 设置为 WebGrid 对象的数据源。在页面正文的 HTML 中,行 { @grid.GetHtml() }
将对象的数据呈现为 HTML 表格。
urlTable 是一个 HashSet 对象(一个泛型集合)。每次刷新页面时,都会通过调用 grabber.Search() 向 urlTable 添加更多 URL。为了防止在回发(页面刷新)之间丢失 urlTable 对象,它被保存在页面的 Session 对象中。此表中的行类型为 RowSchema(在 App_Code 文件夹中的 Grabber.cs 文件中定义的类)。为了避免重复的 URL,我们选择了一个 HashSet<RowSchema> 集合。这需要为元素的类型(在本例中为 RowSchema)定义一些 GetHashCode() 和 Equals() 方法的重写。
对 Search() 的调用每次页面刷新大约需要 10 秒钟。如果对 Search() 的调用返回 true,则刷新过程将终止,这种情况发生在行数达到 MaxRecords 并且所有行都被访问时。
Grabber 类
下表列出了一些在 Grabber 类中定义的关键方法。
方法 | 描述 |
public bool Search(string searchWords, int MaxRecords, HashSet<RowSchema> urlTable) |
Grabber 类中的主要(入口点)方法。它将行添加到 (删除或更新) urlTable (从 urlTable 中)。如果行数达到 MaxRecords 并且所有行都被访问,则该方法返回 true。 |
string FetchURL(string url) |
获取给定 url 的 html。它使用 .NET WebRquest 类。 |
string GetTitle(string htmlHead, string searchWords) |
返回页面的标题。如果未找到标题,或者在 searchWords 中未找到任何单词,则返回空字符串。 |
int CountWords(string htmlData, string searchWords) |
返回在 htmlData 中与 searchWords 中的单词匹配的数量。 |
HashSet<string> GrabURLs(string htmlData, string parentURL) |
从 htmlData 中找到的 URL 返回一组绝对 URL。 |
以下列表显示了 Grabber 类中的 Search() 方法。
public bool Search(string searchWords, int MaxRecords, HashSet<RowSchema> urlTable)
{ // Uncomment next line for logging
// logFile = new StreamWriter(Server.MapPath("") + @"\log.txt");
DateTime t1 = DateTime.Now;
while (true)
{ if ((DateTime.Now - t1).TotalSeconds > MaxServerTime) break;
string SearchUrl = String.Format("http://www.bing.com/search?q={0}" ,
HttpUtility.UrlEncode(searchWords)) + "&first=" + rand.Next(500);
string parentURL = "";
RowSchema row1 = null;
if ((urlTable.Count > 5) && (rand.NextDouble() < 0.5))
{ var foundRows = urlTable.Where(p => p.Visited== false).ToList<RowSchema>();
if ((foundRows.Count == 0) && (urlTable.Count == MaxRecords))
return true; // All visited; use to disable refresh timer
if (foundRows.Count > 0)
{ row1 = foundRows[0];
SearchUrl = row1.URL;
row1.Visited = true; // Optimistic that call to FetchURL() will be OK
parentURL = SearchUrl;
}
}
string searchData = FetchURL(SearchUrl);
if (searchData.StartsWith("Error"))
{ if (row1!= null)
{ urlTable.Remove(row1); }
continue;
}
// Debugging: Response.Write(searchData); return;
int i = searchData.IndexOf("<body", StringComparison.InvariantCultureIgnoreCase);
if (i == -1)
{ if (row1 != null)
{ urlTable.Remove(row1); }
continue;
}
string htmlHead = searchData.Substring(0,i-1);
string htmlBody = searchData.Substring(i).ToLower();
if (row1 != null)
{ string Title = GetTitle(htmlHead, searchWords);
if (Title == "")
{ urlTable.Remove(row1);
continue;
}
int Count = CountWords(htmlBody,searchWords);
if (Count == 0)
{ urlTable.Remove(row1);
continue;
}
row1.Title = Title;
row1.Count = Count;
}
foreach (string s in urlSet)
{ if (urlTable.Count == MaxRecords) break;
row1 = new RowSchema();
row1.URL = s;
row1.Visited = false;
// Note: HashSet collection guarantees uniqueness (no duplicate)
// based on the override for Equals()
// row1 won't be added if there is match in urlTable
urlTable.Add(row1);
}
}
if (logFile != null) logFile.Close();
return false;
}
调用 FetchURL(SearchUrl) 用于获取内容,其中 SearchURL 是对 Bing 的搜索查询或来自 urlTable 的一些未访问的 URL。然后处理返回的内容 (searchData) 以使用调用 GrabURLs(searchData) 提取 URL,该调用返回一组 URL (urlSet)。最后,将 urlSet 中的 url 添加到 urlTable 中。
历史
- 2014 年 9 月 11 日:版本 1.0