65.9K
CodeProject 正在变化。 阅读更多。
Home

URL 抓取器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.75/5 (8投票s)

2014年9月11日

CPOL

3分钟阅读

viewsIcon

24867

downloadIcon

759

本文描述了一个 ASP.NET Web Pages 项目,用于抓取与某些词汇高度匹配的页面的 URL。

GrabWords Image

引言

在这篇文章中,我描述了一个 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
© . All rights reserved.