副问题[/!--empirenews.page--]
这是 Java 爬虫系列博文的第五篇,在上一篇 Java 爬虫处事器被屏障,不要慌,咱们换一台处事器 中,我们简朴的聊反爬虫计策和反反爬虫要领,首要针对的是 IP 被封及其对应步伐。前面几篇文章我们把爬虫相干的根基常识都讲的差不多啦。这一篇我们来聊一聊爬虫架构相干的内容。

前面几章内容我们的爬虫措施都是单线程,在我们调试爬虫措施的时辰,单线程爬虫没什么题目,可是当我们在线上情形行使单线程爬虫措施去收罗网页时,单线程就袒暴露了两个致命的题目:
- 收罗服从出格慢,单线程之间都是串行的,下一个执行举措必要等上一个执行完才气执行
- 对处事器的CUP等操作率不高,想想我们的处事器都是 8核16G,32G 的只跑一个线程会不会太挥霍啦
线上情形不行能像我们当地测试一样,不在乎收罗服从,只要能正确提取功效就行。在这个时刻就是款子的年月,不行能给你时刻去逐步的收罗,以是单线程爬虫措施是行不通的,我们必要将单线程改成多线程的模式,来晋升收罗服从和进步计较机操作率。
多线程的爬虫措施计划比单线程就要伟大许多,可是与其他营业在高并发下要担保数据安详又差异,多线程爬虫在数据安详上到要求不是那么的高,由于每个页面都可以被看作是一个独立体。要做许多几何线程爬虫就必需做好两点:第一点就是同一的待收罗 URL 维护,第二点就是 URL 的去重, 下面我们简朴的来聊一聊这两点。
维护待收罗的 URL
多线程爬虫措施就不能像单线程那样,每个线程独自维护这本身的待收罗 URL,假如这样的话,那么每个线程收罗的网页将是一样的,你这就不是多线程收罗啦,你这是将一个页面收罗的多次。基于这个缘故起因我们就必要将待收罗的 URL 同一维护,每个线程从同一 URL 维护处领取收罗 URL ,完成收罗使命,假如在页面上发明新的 URL 链接则添加到 同一 URL 维护的容器中。下面是几种适实用作同一 URL 维护的容器:
- JDK 的安详行列,譬喻 LinkedBlockingQueue
- 高机能的 NoSQL,好比 Redis、Mongodb
- MQ 动静中间件
URL 的去重
URL 的去重也是多线程收罗的要害一步,由于假如不去重的话,那么我们将收罗到大量一再的 URL,这样并没有晋升我们的收罗服从,好比一个分页的消息列表,我们在收罗第一页的时辰可以获得 2、3、4、5 页的链接,在收罗第二页的时辰又会获得 1、3、4、5 页的链接,待收罗的 URL 行列中将存在大量的列表页链接,这样就会一再收罗乃至进入到一个死轮回傍边,以是就必要 URL 去重。URL 去重的要领就很是多啦,下面是几种常用的 URL 去重方法:
- 将 URL 生涯到数据库举办去重,好比 redis、MongoDB
- 将 URL 放到哈希表中去重,譬喻 hashset
- 将 URL 颠末 MD5 之后生涯到哈希表中去重,对比于上面一种,可以或许节省空间
- 行使 布隆过滤器(Bloom Filter)去重,这种方法可以或许节省大量的空间,就是不那么精确。
关于多线程爬虫的两个焦点常识点我们都知道啦,下面我画了一个简朴的多线程爬虫架构图,如下图所示:

多线程爬虫架构图
上面我们首要相识了多线程爬虫的架构计划,接下来我们不妨来试试 Java 多线程爬虫,我们以收罗虎扑消息为例来拭魅战一下 Java 多线程爬虫,Java 多线程爬虫中计划到了 待收罗 URL 的维护和 URL 去重,因为我们这里只是演示,以是我们就行使 JDK 内置的容器来完成,我们行使 LinkedBlockingQueue 作为待收罗 URL 维护容器,HashSet 作为 URL 去重容器。下面是 Java 多线程爬虫焦点代码,具体代码以上传 GitHub,地点在文末:
- /**
- * 多线程爬虫
- */
- public class ThreadCrawler implements Runnable {
- // 收罗的文章数
- private final AtomicLong pageCount = new AtomicLong(0);
- // 列表页链接正则表达式
- public static final String URL_LIST = "https://voice.hupu.com/nba";
- protected Logger logger = LoggerFactory.getLogger(getClass());
- // 待收罗的行列
- LinkedBlockingQueue<String> taskQueue;
- // 收罗过的链接列表
- HashSet<String> visited;
- // 线程池
- CountableThreadPool threadPool;
- /**
- *
- * @param url 起始页
- * @param threadNum 线程数
- * @throws InterruptedException
- */
- public ThreadCrawler(String url, int threadNum) throws InterruptedException {
- this.taskQueue = new LinkedBlockingQueue<>();
- this.threadPool = new CountableThreadPool(threadNum);
- this.visited = new HashSet<>();
- // 将起始页添加到待收罗行列中
- this.taskQueue.put(url);
- }
-
- @Override
- public void run() {
- logger.info("Spider started!");
- while (!Thread.currentThread().isInterrupted()) {
- // 从行列中获取待收罗 URL
- final String request = taskQueue.poll();
- // 假如获取 request 为空,而且当前的线程采已经没有线程在运行
- if (request == null) {
- if (threadPool.getThreadAlive() == 0) {
- break;
- }
- } else {
- // 执行收罗使命
- threadPool.execute(new Runnable() {
- @Override
- public void run() {
- try {
- processRequest(request);
- } catch (Exception e) {
- logger.error("process request " + request + " error", e);
- } finally {
- // 收罗页面 +1
- pageCount.incrementAndGet();
- }
- }
- });
- }
- }
- threadPool.shutdown();
- logger.info("Spider closed! {} pages downloaded.", pageCount.get());
- }
-
- /**
- * 处理赏罚收罗哀求
- * @param url
- */
- protected void processRequest(String url) {
- // 判定是否为列表页
- if (url.matches(URL_LIST)) {
- // 列表页理会出详情页链接添加到待收罗URL行列中
- processTaskQueue(url);
- } else {
- // 理会网页
- processPage(url);
- }
- }
- /**
- * 处理赏罚链吸取罗
- * 处理赏罚列表页,将 url 添加到行列中
- *
- * @param url
- */
- protected void processTaskQueue(String url) {
- try {
- Document doc = Jsoup.connect(url).get();
- // 详情页链接
- Elements elements = doc.select(" div.news-list > ul > li > div.list-hd > h4 > a");
- elements.stream().forEach((element -> {
- String request = element.attr("href");
- // 判定该链接是否存在行列可能已收罗的 set 中,不存在则添加到行列中
- if (!visited.contains(request) && !taskQueue.contains(request)) {
- try {
- taskQueue.put(request);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }));
- // 列表页链接
- Elements list_urls = doc.select("div.voice-paging > a");
- list_urls.stream().forEach((element -> {
- String request = element.absUrl("href");
- // 判定是否切合要提取的列表链接要求
- if (request.matches(URL_LIST)) {
- // 判定该链接是否存在行列可能已收罗的 set 中,不存在则添加到行列中
- if (!visited.contains(request) && !taskQueue.contains(request)) {
- try {
- taskQueue.put(request);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- }));
-
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- /**
- * 理会页面
- *
- * @param url
- */
- protected void processPage(String url) {
- try {
- Document doc = Jsoup.connect(url).get();
- String title = doc.select("body > div.hp-wrap > div.voice-main > div.artical-title > h1").first().ownText();
-
- System.out.println(Thread.currentThread().getName() + " 在 " + new Date() + " 收罗了虎扑消息 " + title);
- // 将收罗完的 url 存入到已经收罗的 set 中
- visited.add(url);
-
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
-
- public static void main(String[] args) {
-
- try {
- new ThreadCrawler("https://voice.hupu.com/nba", 5).run();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
(编辑:湖南网)
【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!
|