java爬虫抓取动态网页(Java爬虫服务器被屏蔽,不要慌,咱们换一台中)

优采云 发布时间: 2022-03-27 04:09

  java爬虫抓取动态网页(Java爬虫服务器被屏蔽,不要慌,咱们换一台中)

  Java爬虫服务器被封了,别慌,换个服务器,我们简单说一下反爬策略和反反爬方法,主要是IP封堵及其对应的方法。之前的文章文章我们讲了爬虫的基础知识。在这篇文章中,我们来谈谈爬虫架构。

  

  在前面的章节中,我们的爬虫程序都是单线程的。我们在调试爬虫程序的时候,单线程爬虫是没有问题的,但是当我们使用单线程爬虫程序去在线环境下的采集网页时,单线程爬虫会不行。暴露了两个致命问题:

  线上的环境不能像我们本地的测试一样,不关心采集的效率,只要能正确提取结果即可。在这个时间就是金钱的时代,不可能给你时间慢慢来采集,所以单线程爬虫程序就不行了,我们需要把单线程改成多线程模式来改进采集 效率和提高计算机利用率。想学习交流HashMap、nginx、dubbo、Spring MVC、分布式、高性能、高可用、MySQL、redis、jvm、多线程、netty、kafka、家微信(佟英文):1253431195 扩展栏目获取资料供学习,无不加工作经验!

  多线程爬虫的设计要比单线程的程序复杂得多,但又不同于其他需要在高并发下保证数据安全的业务。被视为一个独立的实体。做多线程爬虫,必须做好两件事:第一点是维护统一的采集 URL,第二点是对URL进行去重。简单说一下这两点。

  将 URL 维护为 采集

  多线程爬虫不能像单线程一样,每个线程独立维护自己的采集 URL,如果是这样的话,那么每个线程的网页采集都是一样的,你会不是多线程采集,而是要多次采集 一个页面。为此,我们需要将 URL 统一维护为 采集。每个线程从统一的URL维护处接收到采集 URL,完成采集任务。如果在页面上找到新 URL 链接将添加到统一 URL 维护的容器中。这里有几个适合统一 URL 维护的容器:

  URL重复数据删除

  URL去重也是多线程采集中的一个关键步骤,因为如果我们不去重,那么我们会采集到大量重复的URL,这并不能改善我们的采集 效率,例如在一个分页的新闻列表中,我们可以在 采集 的第一页获取到 2、3、4、5 个页面的链接,并且在采集的第二页会再次得到1、3、4、5个页面链接,采集的URL队列中会有大量列表页面链接@>,会重复采集甚至进入死循环,所以需要进行URL去重。有很多方法可以对 URL 进行重复数据删除。以下是几种常用的URL去重方法:

  关于多线程爬虫的两个核心知识点大家都知道。下面我画一个简单的多线程爬虫架构图,如下图所示:

  

  多线程爬虫架构图

  以上,我们主要了解了多线程爬虫的架构设计。接下来,我们不妨试试Java多线程爬虫。我们以采集虎扑新闻为例,实战Java多线程爬虫,在Java多线程爬虫中设计的采集URL的维护和URL的去重,由于这里只是演示,所以我们将使用JDK的内置容器来完成它。我们使用 LinkedBlockingQueue 作为 URL 维护容器为 采集,HashSet 作为 URL 到 Heavy 容器。以下是Java多线程爬虫的核心代码。详细代码可以上传到 GitHub。地址在文末:想学习和交流HashMap,nginx,dubbo,Spring MVC,分布式,高性能高可用,

  2. public class ThreadCrawler implements Runnable {

3. // 采集的文章数

4. private final AtomicLong pageCount = new AtomicLong(0);

5. // 列表页链接正则表达式

6. public static final String URL_LIST = “https://voice.hupu.com/nba”;

7. protected Logger logger = LoggerFactory.getLogger(getClass());

8. // 待采集的队列

9. LinkedBlockingQueue taskQueue;

10. // 采集过的链接列表

11. HashSet visited;

12. // 线程池

13. CountableThreadPool threadPool;

15. public ThreadCrawler(String url, int threadNum) throws InterruptedException {

16. this.taskQueue = new LinkedBlockingQueue();

17. this.threadPool = new CountableThreadPool(threadNum);

18. this.visited = new HashSet();

19. // 将起始页添加到待采集队列中

20. this.taskQueue.put(url);

21. }

23. @Override

24. public void run() {

25. logger.info(“Spider started!”);

26. while (!Thread.currentThread().isInterrupted()) {

27. // 从队列中获取待采集 URL

28. final String request = taskQueue.poll();

29. // 如果获取 request 为空,并且当前的线程采已经没有线程在运行

30. if (request == null) {

31. if (threadPool.getThreadAlive() == 0) {

32. break;

33. }

34. } else {

35. // 执行采集任务

36. threadPool.execute(new Runnable() {

37. @Override

38. public void run() {

39. try {

40. processRequest(request);

41. } catch (Exception e) {

42. logger.error(“process request “ + request + ” error”, e);

43. } finally {

44. // 采集页面 +1

45. pageCount.incrementAndGet();

46. }

47. }

48. });

49. }

50. }

51. threadPool.shutdown();

52. logger.info(“Spider closed! {} pages downloaded.”, pageCount.get());

53. }

55. protected void processRequest(String url) {

56. // 判断是否为列表页

57. if (url.matches(URL_LIST)) {

58. // 列表页解析出详情页链接添加到待采集URL队列中

59. processTaskQueue(url);

60. } else {

61. // 解析网页

62. processPage(url);

63. }

64. }

66. protected void processTaskQueue(String url) {

67. try {

68. Document doc = Jsoup.connect(url).get();

69. // 详情页链接

70. Elements elements = doc.select(” div.news-list > ul > li > div.list-hd > h4 > a”);

71. elements.stream().forEach((element -> {

72. String request = element.attr(“href”);

73. // 判断该链接是否存在队列或者已采集的 set 中,不存在则添加到队列中

74. if (!visited.contains(request) && !taskQueue.contains(request)) {

75. try {

76. taskQueue.put(request);

77. } catch (InterruptedException e) {

78. e.printStackTrace();

79. }

80. }

81. }));

82. // 列表页链接

83. Elements list_urls = doc.select(“div.voice-paging > a”);

84. list_urls.stream().forEach((element -> {

85. String request = element.absUrl(“href”);

86. // 判断是否符合要提取的列表链接要求

87. if (request.matches(URL_LIST)) {

88. // 判断该链接是否存在队列或者已采集的 set 中,不存在则添加到队列中

89. if (!visited.contains(request) && !taskQueue.contains(request)) {

90. try {

91. taskQueue.put(request);

92. } catch (InterruptedException e) {

93. e.printStackTrace();

94. }

95. }

96. }

97. }));

99. } catch (Exception e) {

100. e.printStackTrace();

101. }

102. }

104. protected void processPage(String url) {

105. try {

106. Document doc = Jsoup.connect(url).get();

107. String title = doc.select(“body > div.hp-wrap > div.voice-main > div.artical-title > h1”).first().ownText();

109. System.out.println(Thread.currentThread().getName() + ” 在 “ + new Date() + ” 采集了虎扑新闻 “ + title);

110. // 将采集完的 url 存入到已经采集的 set 中

111. visited.add(url);

113. } catch (IOException e) {

114. e.printStackTrace();

115. }

116. }

118. public static void main(String[] args) {

120. try {

121. new ThreadCrawler(“https://voice.hupu.com/nba”, 5).run();

122. } catch (InterruptedException e) {

123. e.printStackTrace();

124. }

125. }

126. }

  我们用5个线程去采集hupu新闻列表页面看看效果。如果我们运行程序,我们会得到以下结果:

  

  多线程采集结果

  从结果可以看出,我们启动了 5 个线程 采集 到 61 个页面,总共耗时 2 秒。可以说效果还是不错的。我们用单线程对比一下,看看差距有多大?我们将线程数设置为1,再次启动程序,得到如下结果:

  

  单线程运行结果

  可以看到,单线程采集Hupu 61消息耗时7秒,几乎是多线程的4倍。如果你仔细想想,这只有 61 页。如果有更多的页面,差距会增加。该值越大,多线程爬虫的效率越高。想学交流HashMap、nginx、dubbo、Spring MVC、分布式、高性能、高可用、MySQL、redis、jvm、多线程、netty、kafka、家微信(佟英文):1253431195 扩展栏目获取资料供学习,无不加工作经验!

  分布式爬虫架构

  分布式爬虫架构是只有大型采集程序才需要使用的架构。一般可以使用单机多线程来解决业务需求。反正我没有分布式爬虫项目的经验,所以和这个没什么关系。是的,但作为技术人员,我们需要保持技术的热度。虽然我们不需要它,但理解它是可以的。我查了很多资料,得出以下结论:

  分布式爬虫架构在思路上和我们的多线程爬虫架构是一样的。我们只需要在多线程的基础上稍加改进,就可以成为一个简单的分布式爬虫架构。由于在分布式爬虫架构中爬虫程序部署在不同的机器上,我们等待的URL采集和已经是采集的URL不能存储在爬虫程序机器的内存中,我们需要存储在某台机器上统一维护,比如存储在Redis或者MongoDB中。每台机器都从这里获取采集链接,而不是从LinkedBlockingQueue等内存队列中获取链接。这样一个简单的分发爬虫架构就出现了。当然会有很多细节,因为我没有分布式架构的经验,也谈不上。如果您有兴趣,欢迎交流。

0 个评论

要回复文章请先登录注册


官方客服QQ群

微信人工客服

QQ人工客服


线