现在互联网上有很多数据。通常,需要对其进行提取和分析,以便进行各种营销研究和商业决策。必要时,应迅速有效地进行。
为什么需要收集和分析数据?由于各种原因,可能有必要:
- 进行网站审计;
- 从网上商店收集数据;
- 神经网络训练集的准备;
- 监控社交网络,新闻,博客的评论;
- 分析网站内容,如识别网站上的死链接等。
数据不仅限于文本,还可以是图像、视频、表格、各种文件等。人们可能需要提取链接和文本,通过关键字或短语进行搜索,收集图片等。
另一个重要的任务是监测网站的健康状况,检查是否存在死链接,以及网站是否普遍可用。
所有这些都需要现成的工具。
# 现有解决方案
收集和分析数据的最常用方法是向Web服务器发送请求,接收和处理HTML中的响应。对此HTML进行解释,您可以提取必要的信息。
然而,现代网页积极使用JavaScript,内容在许多页面上被动态加载或形成。仅仅从Web服务器获得响应是不够的,因为它可能只是一个带有大量JavaScript的引导页面,执行JavaScript会生成必要的内容。
# 如何使用Web浏览器功能?
> 网站是为人们创建的。人们通过浏览器访问网站。
使用Web浏览器收集数据怎么样?它将消除向Web服务器发送请求的方法的许多限制。毕竟,您需要登录某些站点并在页面上执行几个操作才能获得结果。如果我们能控制浏览器的用户代理,这样服务器就不会认为我们是机器人了,这将是最好的选择。它还有助于接收面向桌面的内容,而不仅仅是移动设备的一些简化版本。
在本文中,我们将研究使用Web浏览器的功能来收集数据的方法。特别是,我们将收集指定网站上的所有链接,并检查其中是否有任何损坏的链接,即导致因任何原因导致不可用页面的链接。我们将通过JxBrowser图书馆。
JxBrowser是一个商业Java库,允许您在商业Java应用程序中使用铬的功能。对于开发和销售使用Java技术创建的软件解决方案或需要为满足内部需要而创建的Java应用程序提供先进可靠的Web浏览器组件的公司来说,这是很有帮助的。
# 在开始之前你应该做什么?
在开始设计解决方案和编写代码之前,我们应该考虑哪些要点?我们需要从网站的一个特定页面开始。它可以是主页,也可以是网站地址。
在页面上,我们应该找到指向其他页面的链接。链接可能导致其他网站(外部)和相同的网站页面(内部)。另外,链接并不总是指向另一个页面。其中一些链接会带访问者访问同一页面的某个部分(此类链接通常以 *#* )。其中有些不是链接本身,而是通过电子邮件发送行动快捷键-*邮件:* .
由于链接可能是循环的,我们需要特别小心。为了正确处理循环引用,我们需要记住我们已经访问过的页面。
同样重要的是要检查链接所指向的页面是否不可用,并且需要获得一个错误代码来解释为什么无法访问它。
# 算法
考虑到上面的所有信息,让我们来思考一下基于Web浏览器的程序是如何工作的。
1. 启动网页浏览器。
1. 加载必要的网页。
1. 如果加载了页面,访问它的DOM并找到所有的锚HTML元素。对于其中的每一个,获取**Href**每个组件的值。这样,我们将得到所有的链接在页面上。
1. 如果页面没有加载,请记住网页服务器的错误。
1. 记住处理过的页面。
1. 如果该页属于我们的网站,请过滤链接;删除那些我们不感兴趣的链接,例如指向页面的子部分的链接或mailto:。
1. 浏览收到的链接列表。
1. 对于列表中的每一页,请遵循从第1页开始的步骤。
1. 如果它是一个外部页面,那么记住它,但不要分析它的链接。我们只对我们网站网页上的链接感兴趣。
1. 在我们处理完所有已发现的页面后,我们完成了导航。
1. 关闭网页浏览器。
1. 浏览所有分析过的页面,找出那些链接中断的页面。
下面是流程图形式的程序算法。
![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/fa8f36881b4f484b938afee06e3b1ef4~tplv-k3u1fbpfcp-zoom-1.image)
# 实施
让我们看看如何实现主要阶段。
启动网页浏览器:
```
engine = Engine.newInstance(
EngineOptions.newBuilder(OFF_SCREEN).build());
browser = engine.newBrowser();
```
加载网页:
```
browser.navigation().loadUrlAndWait(url, Duration.ofSeconds(30));
```
获取DOM访问和搜索链接:
```
browser.mainFrame().flatMap(Frame::document).ifPresent(document ->
// Collect the links by analyzing the HREF attribute of
// the Anchor HTML elements.
document.findElementsByTagName("a").forEach(element -> {
try {
String href = element.attributeValue("href");
toUrl(href, browser.url()).ifPresent(
url -> result.add(Link.of(url)));
} catch (IllegalStateException ignore) {
// DOM of a web page might be changed dynamically
// from JavaScript. The DOM HTML Element we analyze,
// might be removed during our analysis. We do not
// analyze attributes of the removed DOM elements.
}
}));
```
获取HTML页面:
```
/**
* Returns a string that represents HTML of
* the currently loaded web page.
*/
private String html(Browser browser) {
AtomicReference<String> htmlRef = new AtomicReference<>("");
browser.mainFrame().ifPresent(frame ->
htmlRef.set(frame.html()));
return htmlRef.get();
}
```
分析网站的领导阶层的例子。
```
package com.teamdev.jxbrowser.examples.webcrawler;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.teamdev.jxbrowser.engine.RenderingMode.OFF_SCREEN;
import com.google.common.collect.ImmutableSet;
import com.teamdev.jxbrowser.browser.Browser;
import com.teamdev.jxbrowser.engine.Engine;
import com.teamdev.jxbrowser.engine.EngineOptions;
import java.io.Closeable;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
/**
* A web crawler implementation that is based on JxBrowser that
* allows discovering and analyzing the web pages, accessing
* their DOM and HTML content, finding the broken links on a web
* page, etc.
*/
public final class WebCrawler implements Closeable {
/**
* Creates a new {@code WebCrawler} instance for the given
* target {@code url}.
*
* @param url the URL of the target web page the crawler
* will start its analysis
* @param factory the factory used to create a {@link
* WebPage} instance for the internal and
* external URLs
*/
public static WebCrawler newInstance(String url,
WebPageFactory factory) {
return new WebCrawler(url, factory);
}
private final Engine engine;
private final Browser browser;
private final String targetUrl;
private final Set<WebPage> pages;
private final WebPageFactory pageFactory;
private WebCrawler(String url, WebPageFactory factory) {
checkNotNull(url);
checkNotNull(factory);
targetUrl = url;
pageFactory = factory;
pages = new HashSet<>();
engine = Engine.newInstance(
EngineOptions.newBuilder(OFF_SCREEN)
// Visit the web pages in the Chromium's
// incognito mode.
.enableIncognito()
.build());
browser = engine.newBrowser();
}
/**
* Starts the web crawler and reports the progress via the
* given {@code listener}. The time required to analyze web
* site depends on the number of discovered web pages.
*
* <p>This operation blocks the current thread execution
* until the crawler stops analyzing the discovered web pages.
*
* @param listener a listener that will be invoked to report
* the progress
*/
public void start(WebCrawlerListener listener) {
checkNotNull(listener);
analyze(targetUrl, pageFactory, listener);
}
private void analyze(String url, WebPageFactory factory,
WebCrawlerListener listener) {
if (!isVisited(url)) {
WebPage webPage = factory.create(browser, url);
pages.add(webPage);
// Notify the listener that a web page
// has been visited.
listener.webPageVisited(webPage);
// If it is an external web page, do not go
// through its links.
if (url.startsWith(targetUrl)) {
webPage.links().forEach(
link -> analyze(link.url(), factory,
listener));
}
}
}
/**
* Checks if the given {@code url} belongs to an already
* visited web page.
*/
private boolean isVisited(String url) {
checkNotNull(url);
return page(url).orElse(null) != null;
}
/**
* Returns an immutable set of the web pages that are already
* analyzed by this crawler.
*/
public ImmutableSet<WebPage> pages() {
return ImmutableSet.copyOf(pages);
}
/**
* Returns an {@code Optional} that contains a web page
* associated with the given {@code url} or an empty options
* if there is no such web page.
*/
public Optional<WebPage> page(String url) {
checkNotNull(url);
for (WebPage page : pages) {
if (page.url().equals(url)) {
return Optional.of(page);
}
}
return Optional.empty();
}
/**
* Releases all allocated resources and closes the web
* browser used to discover and analyze the web pages.
*/
@Override
public void close() {
engine.close();
```
# 结果
如果我们编译并运行该程序,我们应该得到以下输出:
```
https://teamdev.com/jxbrowser [OK]
https://www.teamdev.com/about [OK]
https://jxbrowser-support.teamdev.com/docs/guides/dialogs.html [OK]
https://www.teamdev.com/jxcapture [OK]
https://jxbrowser-support.teamdev.com/javadoc/7.13/com/teamdev/jxbrowser/view/javafx/BrowserView.html [OK]
https://spine.io [OK]
https://jxbrowser.support.teamdev.com/support/tickets [OK]
https://sos-software.com [OK]
...
Dead or problematic links:
https://www.teamdev.com/jxbrowser
https://www.shi.com CONNECTION_TIMED_OUT
http://www.comparex-group.com ABORTED
http://www.insight.com NAME_NOT_RESOLVED
https://www.swnetwork.de/swnetwork ADDRESS_UNREACHABLE
...
Process finished with exit code 0
```
# 细微差别、问题和解决办法
以下是在各种网站上实现和测试该解决方案过程中遇到的一些细微差别。
许多web服务器被保护不受dDoS攻击,频繁的请求被错误代码拒绝。**流产**。要从网站上删除负载并进行“礼貌”分析,您需要使用超时。在程序中,我们使用了500毫秒的延迟。不幸的是,即使有这样的延迟,Web服务器也拒绝我们的请求。
我们不会放弃并尝试以不同的间隔加载页面:
```
/**
* Loads the given {@code url} and waits until the web page
* is loaded completely.
*
* @return {@code true} if the web page has been loaded
* successfully. If the given URL is dead or we didn't manage
* to load it within 45 seconds, returns {@code false}.
*
* @implNote before every navigation we wait for {@link
* #NAVIGATION_DELAY_MS} because web server may abort often
* URL requests to protect itself from DDoS attacks.
*/
private NetError loadUrlAndWait(Browser browser, String url,
int navigationAttempts) {
// All our attempts to load the given url were rejected (
// We give up and continue processing other web pages.
if (navigationAttempts == 0) {
return NetError.ABORTED;
} try {
// Web server might abort often URL requests to
// protect itself from DDoS attack. Use a delay
// between URL requests.
long timeout = (long) NAVIGATION_DELAY_MS
* navigationAttempts;
TimeUnit.MILLISECONDS.sleep(timeout); // Load the given URL and wait until web page
// is loaded completely.
browser.navigation()
.loadUrlAndWait(url, Duration.ofSeconds(30));
} catch (NavigationException e) {
NetError netError = e.netError();
if (netError == NetError.ABORTED) {
// If web server aborts our request, try again.
return loadUrlAndWait(browser, url,
--navigationAttempts);
}
return netError;
} catch (TimeoutException e) {
// Web server did not respond within 30 seconds (
return NetError.CONNECTION_TIMED_OUT;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} return NetError.OK;
}
```
Web服务器可以在加载页面时进行重定向。当我们加载一个地址,我们到达另一个地址。我们可以记住这两个地址,但重要的是停止只记住应用程序中请求的地址。
Web服务器可能根本不响应某些请求。因此,在加载页面时,我们需要使用超时等待下载。如果在此超时期间未加载该页,则将该页标记为不可用错误。**连接超时**.
在某些网页上,DOM模型可能会在页面加载后立即更改。因此,在分析DOM模型时,我们必须处理一些DOM元素可能不遍历DOM树的情况。
```
browser.mainFrame().flatMap(Frame::document).ifPresent(document ->
// Collect the links by analyzing the HREF attribute of
// the Anchor HTML elements.
document.findElementsByTagName("a").forEach(element -> {
try {
String href = element.attributeValue("href");
toUrl(href, browser.url()).ifPresent(
url -> result.add(Link.of(url)));
} catch (IllegalStateException ignore) {
// DOM of a web page might be changed dynamically
// from JavaScript. The DOM HTML Element we analyze,
// might be removed during our analysis. We do not
// analyze attributes of the removed DOM elements.
}
}));
```
当然,在你可能会遇到的分析中,还有很多其他网站的细微差别。幸运的是,Web浏览器的功能允许您解决其中的大多数问题。
# 结论
你可以使用Web浏览器创建JavaCrawler,由于我们的经验,这是一种更自然的网站交流方式。许多SEO蜘蛛和WebCrawler工具在专业软件市场上已经使用了这些基于Web浏览器能力的业务驱动解决方案,这证明了这种方法的有效性。
小伙伴们如果觉得我写的不错请点赞加关注!!!
完整资料已经给大家打包完毕,需要的小伙伴可以点击[获取学习资料](https://docs.qq.com/doc/DWkd2b1FqcmlEeGdK)