最近三年很少写博客,虽然一直从事IT行业,但更多的是管理工作,想想真有些惭愧。
最近半年,时不时业务上需要爬虫,将网页内容爬取分析,有用于AI人工智能分析,有用于大数据分析,种种应用,发现爬虫作用不小
这篇文章不是一篇教学文章,更多的是分享在爬取网页中可能会遇到的一些技术问题以及常见问题,并提供解决方案。因此更适合有一定爬虫开发经验人员阅读。
虽然本文使用Java,但这些常见问题,与所使用的开发语言无关,可更多借鉴。
常见问题一:网页的获取方式
1、直接获取静态页面方式
何为“静态页面”,指页面内容通过HTTP获取的时候,已经是完整页面内容,没有使用Ajax异步调用生成页面内容的网页,即页面内容均在后台生成。
仅对该类页面,使用基于HTTP方式直接加载页面时,才可以获取完整页面内容。
对于Java语言,可以使用JSoup或者HttpClient去抓取,页面获取效率非常高。下面提供参考源码:
JSoup方式:
- /**
- * 获取游记文章页面的HTML对象。
- *
- * @param articleUrl
- * 文章URL地址。
- * @return
- * @throws IOException
- */
- private Document getArticleDocument(String articleUrl) throws IOException {
- Connection conn = Jsoup.connect(articleUrl);
- conn.header("User-Agent",
- "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:29.0) Gecko/20100101 Firefox/29.0");
- Response rs = conn.execute();
- Document doc = Jsoup.parse(rs.body());
- return doc;
- }
HttpClient方式:
- /**
- * 直接通过HTTP请求获取页面内容。
- *
- * @param url
- * @return
- * @throws ClientProtocolException
- */
- public static String getUrlHtml(String url) throws ClientProtocolException,
- IOException {
- HttpClient httpclient = HttpClientBuilder.create().build();
- HttpGet httpGet = new HttpGet(url);
- httpGet.setHeader("User-Agent",
- "Mozilla/5.0 (Windows NT 6.3; W…) Gecko/20100101 Firefox/56.0");
- httpGet.setHeader("Accept",
- "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");
- httpGet.setHeader("Accept-Language",
- "zh-CN,zh;q=0.8,en;q=0.6,zh-TW;q=0.4");
- httpGet.setHeader("Accept-Encoding", "gzip, deflate, sdch");
- httpGet.setHeader("Upgrade-Insecure-Requests", "1");
- HttpResponse response1 = httpclient.execute(httpGet);
- HttpEntity entity = response1.getEntity();
- String html = EntityUtils.toString(entity);
- return html;
- }
HttpClient依赖:
- <dependency>
- <groupId>org.apache.httpcomponents</groupId>
- <artifactId>httpclient</artifactId>
- <version>4.5.2</version>
- </dependency>
JSoup依赖:
- <dependency>
- <!-- jsoup HTML parser library @ https://jsoup.org/ -->
- <groupId>org.jsoup</groupId>
- <artifactId>jsoup</artifactId>
- <version>1.10.3</version>
- </dependency>
- [color=red]
- 此处需要特别强调,对于很多网站,对爬虫都有一定的防范,因此在获取页面时,必须要补齐浏览器信息,否则很容易会导致被封IP!
- [/color]
2、使用模拟浏览器方式获取动态页面
使用模拟浏览器的方式实属被逼无奈,因为这种方式处理速度实在是慢,但对于异步加载内容的网页特别有效。如果你发现直接通过Http拉取页面获取不到元素值的时候,也只能使用这种方法。
HtmlUnit获取网页:
- /**
- * 通过模拟浏览器的方式下载完整页面。
- *
- * @param url
- * @return
- * @throws FailingHttpStatusCodeException
- * @throws IOException
- */
- public static String downloadHtml(String url, int timeout)
- throws FailingHttpStatusCodeException, IOException {
- try (final WebClient webClient = new WebClient(BrowserVersion.CHROME)) {
- webClient.getOptions().setJavaScriptEnabled(true);
- webClient.getOptions().setCssEnabled(false);
- webClient.getOptions().setRedirectEnabled(true);
- webClient.getOptions().setThrowExceptionOnScriptError(false);
- webClient.getOptions().setThrowExceptionOnFailingStatusCode(false);
- webClient
- .setAjaxController(new NicelyResynchronizingAjaxController());
- webClient.getOptions().setTimeout(timeout);
- WebRequest webRequest = new WebRequest(new URL(url));
- webRequest.setHttpMethod(HttpMethod.GET);
- final HtmlPage page = webClient.getPage(webRequest);
- webClient.waitForBackgroundJavaScriptStartingBefore(500);
- webClient.waitForBackgroundJavaScript(20000);
- final String pageAsXml = page.asXml();
- return pageAsXml;
- }
- }
HtmlUnit依赖:
- <dependency>
- <groupId>net.sourceforge.htmlunit</groupId>
- <artifactId>htmlunit</artifactId>
- <version>2.30</version>
- </dependency>
常见问题二:如何避免IP被封
处理方式比较简单,就是不要使用并发,或者连续性的长时间抓取网站内容。最好抓取的时候,保持一定的时间间隔,根据我抓取不同网站的经验,间隔在3-5秒获取一次网页最为保险,一些网站一旦封了IP,可能要一周左右才解锁,因此对于固定外网IP的用户,正在大规模抓取之前必须留意该限制。
以下抓取代码供参考:
- /*
- * (non-Javadoc)
- *
- * @see
- * com.hna.tech.spider.service.SpiderService#setPageArticleDetail(java.lang
- * .String)
- */
- public List<Map<String, String>> setPageArticleDetail(
- List<Map<String, String>> pageList) throws IOException,
- InterruptedException {
- for (Map<String, String> item : pageList) {
- [color=red]
- // 最小延迟3秒,少于3秒将可能被封IP
- Thread.sleep(3000);
- [/color]
- String articleUrl = item.get(KEY_LINK);
- Document doc = getArticleDocument(articleUrl);
- String articleHtml = getArticleContentHtml(doc);
- item.put(KEY_CONTENT_HTML, articleHtml);
- String articleContent = getArticleContent(doc);
- item.put(KEY_CONTENT, articleContent);
- }
- return pageList;
- }
那么如何提升抓取效率呢?一般网站,都会根据Session以及IP进行锁定是否DDOS攻击,或者恶意刷网页,所以问题也就简单了,通过多个线程发起请求,保证每个Session请求的间隔时间。另外,并发也不要太过分,只要不对网站造成压力,一般不会被封IP。
常见问题三:对于元素选取的问题
获取元素的方式有很多种方法,可以通过ID、CSS样式、元素类型(比如<a/> <p/>)等,可根据个人喜好,通常都可以获得元素的内容,比如文章标题、文章正文等等。
以下分享我个人使用JSoup获取页面元素内容的一些心得。
1、阶梯式获取
即优先优先获取顶级节点,比如<Content>是文章内容的顶级节点,那么通过JSoup先获取 第一个顶级节点,然后再获取下级节点的内容,避免当存在多个<Content>节点的时候,会出现获取内容超出预期。
2、CSS样式的选取问题
当存在多个class样式,比如:<div class="css1 css2">...</div>,使用JSoup选取:
- doc.select("div.css1.css2")
使用点号连在一起即可。