如何用Java构建网络爬虫
本教程将向你介绍如何构建一个基本的网络爬虫,并将帮助你理解使网络爬虫工作的基本算法。它还将涵盖一些用例和构建一个爬虫所涉及的挑战。
根据seedscientific.com,我们每天产生2.5万亿字节的数据。这些数据中的很大一部分是通过我们与互联网的互动产生的。
世界各地的大型组织出于商业和研究目的,提取和分析这些数据,以进一步发展他们的业务,同时实现利润最大化。
这些组织如何穿越网络,为他们所期望的目的探索这些现有的数据?这就是网络爬虫的作用。
前提条件
作为前提条件,读者必须具备以下条件。
- Java编程语言的基本知识。
- 一个合适的开发环境,如IntelliJ或你选择的任何其他文本编辑器。
- 正则表达式的基本知识。
什么是网络爬虫?
网络爬虫是网络搜刮工具之一,用于穿越互联网,收集数据并为网络建立索引。它可以被描述为一种自动工具,通过一系列的网页来收集所需信息。
网络爬行有时可与网络搜刮交替使用--网络搜刮是一种从网页中提取数据的实际工作的工具。
网络抓取器从网页中提取数据,将它们组织在一个确定的结构中,并对这些数据执行指定的操作。
网络刮刀与网络爬虫有着本质的区别,前者被用作数据挖掘工具,导航网页并在网页上提取指定的数据,而后者则是为了寻找或发现网络上的URL或链接。
网络爬虫是如何工作的?
你可以把网络爬虫与一个为商店创建商品目录的库存员联系起来。该目录将包含物品的名称、各自的描述、它们在商店中的位置(为了便于搜索)、每件物品的数量,以及任何其他相关信息。
使用这个目录,任何走进商店的人都可以很容易地找到他们想要的物品。
这种体验类似于为商场中的过道命名,使顾客很容易找到同一类别的商品。例如,你肯定能在一个名为 "盥洗用品 "的过道中找到纸巾。
使用网络爬虫,这种编目过程被称为搜索索引。在这种情况下,互联网作为商店,而URLs作为商店里的物品。
网络爬虫爬行互联网--从一个根网页开始。它在根网页的内容中搜索超链接或URL,然后将找到的每个URL保存到一个网页列表中--这些网页随后将被抓取。
在完全抓取根网页后,它选择另一个URL并重复抓取的过程。这可以无限期地持续下去,因为互联网包含了大量的网站集合。
网络爬虫的用例和应用
获取产品数据
组织机构使用网络爬虫导航到其竞争对手的网页,以收集重要的信息,如其价格和任何其他必要的信息,这取决于其领域的背景。
执行这项工作的工具的一些例子是Octoparse和Puppeteer。
为销售和营销团队创造线索
你可以利用网络爬虫获得你的产品和服务的潜在线索的联系信息。
SEO和关键词搜索目的
您可以使用网络爬虫来获取一定时期内您的公司和您的竞争对手在搜索引擎上的排名。这可以帮助你的团队制定最佳策略,以提高你的公司在搜索引擎上的排名表现。
反馈监测
你也可以使用网络爬虫来遍历网络,寻找用户可能提到你公司名字的地方进行评论。
建立网络爬虫的挑战
尽管网络爬虫有很多好处,但在构建网络爬虫时往往会带来一些挑战。所面临的一些问题包括。
服务器过载
当爬虫遍历不相关的网页或浏览大量的网页时,这通常会发生。这可能会影响服务器的性能。
反搜刮工具的存在
反搜刮工具将机器人与人类区分开来,并限制机器人在其网页上进行恶意活动的访问。
各个组织已经开始在他们的网站上整合反搜刮工具,以防止未经授权访问他们的网页。
这些工具可以区分机器人和人类,并限制机器人在其网页上进行恶意活动的访问。
这种工具的一个例子是谷歌验证码。
不一致的网页结构
网站的结构相互之间是不同的。由于这种动态性,在一个网站上表现良好的网络爬虫可能在另一个网站上失败。
用于构建网络爬虫的Java库
尽管本教程只涉及网络爬虫的基本概念,没有使用任何外部库,但这里有一些Java API,你可以将其与你的应用程序集成,以执行网络爬虫。
广度优先搜索(BFS)
在进一步学习本教程之前,你必须对广度优先搜索(BFS)算法有一个基本的了解,以帮助你理解后续章节。
如果你熟悉BFS的操作方式,跳过这一节也没关系。
BFS是一种树形遍历算法,当你想以分层的形式访问一棵树或图数据结构的每一个节点时,就会实现这种算法。
这意味着,给定一个源节点,BFS水平地访问源节点的所有子节点(从左到右或从右到左,取决于你选择如何实现它),然后继续访问下一层的子节点,即源节点的 "孙子"。

在上图中,遍历从Node 1 (父节点或源节点)开始。然后,它继续到它的子节点--第二层的节点2,3,4 。从Node 4 ,遍历继续到第三层的最左边部分,并水平移动到该层的末端。这个过程一直持续到所有的节点都被访问。
BFS的时间复杂度为O(V+E) ,其中V 表示顶点的数量,E 表示边的数量。
构建网络爬虫
现在,在本教程的核心部分,我们将建立一个使用BFS算法来遍历网页的网络爬虫。
该爬虫将从一个源URL开始,访问其中的每一个URL。一旦这个源URL中的每一个URL都被访问过,该算法就会访问子URL中的每一个URL,并顺着这个链条往下走,直到到达你指定的断点。
注意:该断点将代表您希望您的网络爬虫访问多少个URL。这样,它就不会无休止地继续下去。
该算法将只访问以前没有访问过的URL,以确保我们不会循环往复。这些URLs代表顶点,URLs之间的连接是边。
伪代码
- 首先,将根URL添加到一个队列和一个被访问的URL列表中。
- 当队列不是空的时候,从队列中移除URL,并读取其原始HTML内容。
- 在读取URL的HTML内容时,搜索父HTML中包含的任何其他URL。
- 当发现一个新的URL时,通过检查已访问的URL列表来验证它以前没有被访问过。
- 将新发现的未访问的URL添加到队列和已访问的URL列表中。
- 对于在HTML内容中发现的每个新的URL,重复步骤4和5。
- 当HTML中所有的URL都被找到后,从第2步开始重复,直到程序到达你指定的断点。
在这个演示中,我们将使用一个100的断点。
实施
创建一个名为WebCrawler 的Java类,并在文件中添加以下代码。
public class WebCrawler {
private Queue<String> urlQueue;
private List<String> visitedURLs;
public WebCrawler() {
urlQueue = new LinkedList<>();
visitedURLs = new ArrayList<>();
}
}
在上面的代码中,我们已经初始化了类、数据结构和我们随后要使用的构造函数。
public void crawl(String rootURL, int breakpoint) {
urlQueue.add(rootURL);
visitedURLs.add(rootURL);
while(!urlQueue.isEmpty()){
// remove the next url string from the queue to begin traverse.
String s = urlQueue.remove();
String rawHTML = "";
try{
// create url with the string.
URL url = new URL(s);
BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream()));
String inputLine = in.readLine();
// read every line of the HTML content in the URL
// and concat each line to the rawHTML string until every line is read.
while(inputLine != null){
rawHTML += inputLine;
inputLine = in.readLine();
}
in.close();
} catch (Exception e){
e.printStackTrace();
}
// create a regex pattern matching a URL
// that will validate the content of HTML in search of a URL.
String urlPattern = "(www|http:|https:)+[^\s]+[\\w]";
Pattern pattern = Pattern.compile(urlPattern);
Matcher matcher = pattern.matcher(rawHTML);
// Each time the regex matches a URL in the HTML,
// add it to the queue for the next traverse and the list of visited URLs.
breakpoint = getBreakpoint(breakpoint, matcher);
// exit the outermost loop if it reaches the breakpoint.
if(breakpoint == 0){
break;
}
}
}
在crawl() 方法中,rootURL 是爬虫的起点,breakpoint 代表你希望你的爬虫发现多少个URL。
该算法涉及的步骤是。
- 该算法首先将根URL添加到队列和被访问的URL列表中。
- 它使用
BufferedReaderAPI读取URL的每一行HTML内容。 - 然后,它将读取的每一行HTML内容串联到
rawHTML变量中。
private int getBreakpoint(int breakpoint, Matcher matcher) {
while(matcher.find()){
String actualURL = matcher.group();
if(!visitedURLs.contains(actualURL)){
visitedURLs.add(actualURL);
System.out.println("Website found with URL " + actualURL);
urlQueue.add(actualURL);
}
// exit the loop if it reaches the breakpoint.
if(breakpoint == 0){
break;
}
breakpoint--;
}
return breakpoint;
}
上面的代码做了以下事情。
getBreakPoint方法使用指定的regex模式来发现rawHTML中的URLs。- 这些操作一直迭代到爬虫发现了你的
breakpoint中指定的URL数量。
下面是应用程序的主方法的一个片段。
public static void main(String[] args) {
WebCrawler crawler = new WebCrawler();
String rootURL = "https://www.section.io/engineering-education/springboot-antmatchers/";
crawler.crawl(rootURL, 100);
}
上述main 方法将rootURL 设为我的一个教程 https://www.section.io/engineering-education/springboot-antmatchers/ ,并将断点设为100 。
下面是运行该程序的输出快照。

上面快照中的网址是网络爬虫从根网址开始通过嵌入的网址爬行的所有网页中包含的一些网址,直到它到达断点。
总结
本教程帮助你了解了网络爬虫是怎么回事以及它们在现实生活中的使用情况。我们构建了一个网络爬虫,它可以发现父级URL的HTML内容中包含的URL,并在找到指定数量的URL后终止。
我们还简单地介绍了广度优先搜索,这是一种用于构建网络爬虫的算法。
我希望这对你来说是一个好的起点。你可以为你的网络爬虫添加更多的功能,让它拥有更多的能力。