一篇关于java爬虫实现的技术分享

最近由于工作的需要,独自开始研究爬虫爬取互联网数据;经过两周左右的探究,踩过许多坑,也学习到了许多以往不知道的知识。

一直都在做伸手党,很是惭愧_(:_」∠)_感觉都要脸红了☺,在这里总结一下经验,顺便分享给大家,希望可以帮助到有需要的朋友。爬虫技术不是很成熟,如果能有大佬能够不吝赐教那就更好啦~

在网上找了许多资料,爬虫工具大多是用python实现的;因为本身是学java出身,虽说python比java容易,但也没更多时间去学习新的语言了。最终还是选择了用java来实现,废话不多说⁄(⁄ ⁄•⁄ω⁄•⁄ ⁄)⁄下面进入正题。

本篇爬虫技术分享是用java+selenium+phantomjswindows环境中运行,实现了爬取百度的搜索结果。

首先借用网络上的资料来介绍下两个小工具:

selenium

Selenium是一个用于Web应用程序测试的工具。Selenium测试直接运行在浏览器中,就像真正的用户在操作一样。支持的浏览器包括IE、Mozilla Firefox、Mozilla Suite等。这个工具的主要功能包括:测试与浏览器的兼容性——测试你的应用程序看是否能够很好得工作在不同浏览器和操作系统之上。测试系统功能——创建衰退测试检验软件功能和用户需求。支持自动录制动作和自动生成。Net、Java、Perl等不同语言的测试脚本。Selenium 是ThoughtWorks专门为Web应用程序编写的一个验收测试工具。

selenium的使用:

maven配置好就可以自动下载,配置如下

<dependency>

<groupId>org.seleniumhq.selenium</groupId>

<artifactId>selenium-java</artifactId>

<version>2.48.0</version>

</dependency>

自动下载了以下jar包,只用到部分

selenium-java-2.48.0.jar

selenium-chrome-driver-2.48.0.jar

selenium-remote-driver-2.48.0.jar

selenium-edge-driver-2.48.0.jar

selenium-htmlunit-driver-2.48.0.jar

selenium-firefox-driver-2.48.0.jar

selenium-ie-driver-2.48.0.jar

selenium-safari-driver-2.48.0.jar

selenium-support-2.48.0.jar

selenium-leg-rc-2.48.0.jar


如需直接使用jar包请去seleniumhq.org/download 官网中下载。

官网被墙可以移步这里下载selenium-release.storage.googleapis.com

selenium的使用一般搭配浏览器驱动,这里提供chrome浏览器驱动的下载地址chromedriver.storage.googleapis.com

selenium使用中可能会遇到的调用报错的问题,很大的原因是浏览器驱动与浏览器不兼容,以下是chrome浏览器驱动与浏览器兼容的版本对应关系表,根据本地的chrome浏览器下载对应版本的chromedriver

下载完后把压缩包中的chromedriver.exe放入正常的谷歌浏览器安装目录,与chrome.exe同目录中就可以了


phantomjs

(1)一个基于webkit内核的无头浏览器,即没有UI界面,即它就是一个浏览器,只是其内的点击、翻页等人为相关操作需要程序设计实现。

(2)提供JavaScript API接口,即通过编写js程序可以直接与webkit内核交互,在此之上可以结合Java语言等,通过java调用js等相关操作,从而解决了以前c/c++才能比较好的基于webkit开发优质采集器的限制。

(3)提供windows、Linux、mac等不同os的安装使用包,也就是说可以在不同平台上二次开发采集项目或是自动项目测试等工作。

phantomjs 的官网api——API | PhantomJS


phantomjs的使用:


phantomjs官网下载地址phantomjs.org/download.

下载完后是一个压缩包,直接解压即可,解压完后把phantomjs的bin路径配置到系统环境变量path中,配置完后cmd测试出现以下信息则已经可以使用

本篇中的爬虫,整体流程比较简单,细节部分需要多多注意,源码中都有注释,大致如下:

类中导包信息

import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.apache.commons.io.FileUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.openqa.selenium.By;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import jxl.Cell;
import jxl.Sheet;
import jxl.Workbook;

流程开始

1.首先读取excel文件,读取excel中的搜索条件

贴出源码:

	public static List<String> readExcel(String excelFilePath){
		List<String> contents = new ArrayList<String>();
                InputStream in = null;
		try {
		// 1、构造excel文件输入流对象  
	        in = new FileInputStream(excelFilePath);  
	        // 2、声明工作簿对象  
	        Workbook rwb = Workbook.getWorkbook(in);  
	        // 3、获得工作簿的个数,对应于一个excel中的工作表个数  
	        rwb.getNumberOfSheets(); 
	        
	        // 使用索引形式获取第一个工作表,也可以使用rwb.getSheet(sheetName);其中sheetName表示的是工作表的名称
	        Sheet oFirstSheet = rwb.getSheet(0);  
	        int rows = oFirstSheet.getRows();//获取工作表中的总行数,排除第一行
	        int columns = oFirstSheet.getColumns();//获取工作表中的总列数 
	        for (int i = 1; i < rows; i++) {  
	            for (int j = 0; j < columns; j++) {  
	                Cell oCell = oFirstSheet.getCell(j,i);//需要注意的是这里的getCell方法的参数,第一个是指定第几列,第二个参数才是指定第几行
	                String companyName = oCell.getContents();
	                
	                //判断当前文件夹是否已存在(已经完成爬取),如存在则不加入爬取列表中
	                String txtUrl = ThirdPartyProperties.FILEPROFIX + companyName + "/result";//文件路径
	                
	                File file = new File(txtUrl);
			File fileParent = file.getParentFile();
			//文件路径存在则跳过
			if(fileParent.exists()){
				log.info(companyName+"-已经爬取完成,不再加入爬取列表中");
			}else{
				contents.add(companyName);
			}
	            }  
	        }
		} catch (Exception e) {
			log.error(e.getMessage(),e);
		} finally {
			if(null != in){
				try {
					in.close();
				} catch (Exception e2) {
					log.error(e2.getMessage(),e2);
				}
			}
		}
		return contents;
	}

2.使用selenium调用chrome浏览器访问百度,获取到页面元素后模拟输入搜索条件并且百度一下进入搜索结果页,并且获取前count条查询结果的url

贴出源码:

       public static String[] getLinkBySelenium(String keyWord, int count){
		WebDriver driver = null;
		String[] url = new String[count];
		try {
		// 设置 ie 的路径  
//	        System.setProperty("webdriver.ie.driver", "C:\\Program Files\\Internet Explorer\\IEDriverServer.exe");
		// 设置 chrome 的路径  
	        System.setProperty("webdriver.chrome.driver", "你浏览器驱动的全路径");
	        // 创建一个 ie 的浏览器实例  
//	        driver = new InternetExplorerDriver();
	        // 创建一个 chrome 的浏览器实例
	        driver = new ChromeDriver();
		//最大化
		driver.manage().window().maximize();
		//访问百度
		driver.get("http://www.baidu.com");
		//根据页面元素 xpath ,右键元素可获取//*[@id="kw"],这是百度的输入框
		WebElement element = driver.findElement(By.xpath("//*[@id=\"kw\"]"));
		element.sendKeys(keyWord);
		//根据id获取元素 su ,百度一下的按钮
		element = driver.findElement(By.id("su"));
		//点击
		element.click();
			
		//等待5秒,等第count条查询结果加载完
		WebDriverWait wait = new WebDriverWait(driver, 5);
			
	        //等待搜索结果加载完毕,如果报错,说明等待时间过长或者没有搜索结果(百度搜索结果div主键为1,2,3...)
	        try {
		        wait.until(ExpectedConditions.presenceOfElementLocated(By.id(count+"")));
		} catch (Exception e) {
			log.error(keyWord+",该公司百度超时或没有"+count+"条搜索结果---");
		}
	        /**截图保存*/
	        //截图路径
	        String imageUrl = "你要保存截图的路径" + keyWord + "/screenshot.png";//截图路径
	        //指定了OutputType.FILE做为参数传递给getScreenshotAs()方法,其含义是将截取的屏幕以文件形式返回。
	        File srcFile = ((TakesScreenshot)driver).getScreenshotAs(OutputType.FILE);
	        //利用FileUtils工具类的copyFile()方法保存getScreenshotAs()返回的文件对象。
	        FileUtils.copyFile(srcFile, new File(imageUrl));
			
	        for (int i = 1; i <= count; i++) {
	        	//获取页面加载的第一条搜索结果
			WebElement div = driver.findElement(By.id(i+"")).findElement(By.tagName("h3")).findElement(By.tagName("a"));
			//部分公司百度没有搜索结果,在此跳出处理
			if(null == div){
				continue;
			}
			url[i-1] = div.getAttribute("href");
			}
			
		} catch (Exception e) {
			log.error(e.getMessage(),e);
		} finally {
			//关闭浏览器(这个包括驱动完全退出,会清除内存),close 是只关闭浏览器
			driver.quit();
		}
		return url;
	}

3.调用phantomjs访问获取到的url,截图,并通过输入流拿回需要的数据,写入文件保存本地。

贴出源码:

public static void getParseredHtml2(String companyName, String[] url) throws IOException {

		//获取本地项目路径并处理(windows环境下)
		String projectPath = ReptilianWork.class.getClassLoader().getResource("/").getPath();
		projectPath = projectPath.substring(1, projectPath.length()).replace("classes/", "");
//		String projectPath = "D:/Work/workSpace——eclipse/.metadata/.plugins/org.eclipse.wst.server.core/tmp0/wtpwebapps/JavaReptilian/WEB-INF/";
		//js路径
		String jsPath = projectPath + "js/huicong.js";
		
		Date date = new Date();
		SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMddHHmmss");
		String dateName = formatter.format(date);//名称
		
		String imageSuffix = ".png";
		String txtSuffix = ".txt";
		
		for (int i = 1; i <= url.length; i++) {
			
			InputStream in = null;
			FileWriter writer = null;
			String content = "";
			
			try {
				String imageUrl = "你的存储路径" + companyName + "/result"+i + "/image/";//截图路径
				String txtUrl = "你的存储路径" + companyName + "/result"+i + "/txt/";//文件路径
				
				File file = new File(txtUrl+dateName+txtSuffix);
				File fileParent = file.getParentFile();
				//文件路径存在则跳过
//				if(fileParent.exists()){
//					log.info(companyName+"-已经爬取完成,不再爬取");
//					break;
//				}
				Runtime rt = Runtime.getRuntime();
				log.info("phantomjs访问url="+url[i-1]);
				Process p = rt.exec("phantomjs的安装路径(phantomjs.exe的全路径)" + " " + jsPath + " " + url[i-1] + " " + imageUrl + " " + dateName+imageSuffix);
				in = p.getInputStream();
				
				Document doc = Jsoup.parse(in, "UTF-8", url[i-1]);
				content = doc.body().text();
				
				//文件路径不存在则创建
				if(!fileParent.exists()){
					fileParent.mkdirs();
				}
				//创建文件
				file.createNewFile();
				
				writer = new FileWriter(file);
				if(content!=null && !"".equals(content)){
					writer.write(content);
					log.info("文件写入成功,路径:"+txtUrl+dateName+txtSuffix);
				}
				in.close();
				writer.flush();
				writer.close();
			} catch (Exception e) {
				log.error(e.getMessage(),e);
			} finally{
				if(in!=null){
					in.close();
				}
				if(writer!=null){
					writer.close();
				}
			}
		}
	}

以上方法使用了Jsoup来解析java调用phantomjs后拿到的输入流并转成了document对象来操作获取所有文本信息。有关于Jsoup的介绍如下:

jsoup 是一款Java 的HTML解析器,可直接解析某个URL地址、HTML文本内容。它提供了一套非常省力的API,可通过DOM,CSS以及类似于jQuery的操作方法来取出和操作数据。

要使用的话,maven配置如下:

<!-- jsoup 一款Java的HTML解析器 -->

<dependency>

<groupId>org.jsoup</groupId>

<artifactId>jsoup</artifactId>

<version>1.8.3</version>

</dependency>


步骤3方法中使用到的huicong.js文件。注意(一定要设置超时时间,否则遇到一些访问连接有问题或者网络问题等,会导致phantomjs卡住,程序会一直等待响应)

system = require('system')  
address = system.args[1];
imageUrl = system.args[2];
imageName = system.args[3];
var page = require('webpage').create();  
var url = address;
var fs = require('fs');
fs.makeDirectory(imageUrl);

//设置超时时间
page.settings.resourceTimeout = 5000; // 5 seconds
page.onResourceTimeout = function(e) {
    console.log(e.errorCode);   // it'll probably be 408
    console.log(e.errorString); // it'll probably be 'Network timeout on resource'
    console.log(e.url);         // the url whose request timed out
    phantom.exit(1);
};

page.open(url, function (status) {  
    //Page is loaded!  
    if (status !== 'success') {  
        console.log('请求失败!url='+url);
        phantom.exit();
    } else {  
            window.setTimeout(function () {
              page.render(imageUrl+imageName);  //截图
              console.log(page.content);
              phantom.exit();
          }, 5000);   
    } 
  });


源码的缩进由于这边编辑器的问题乱了╮(╯▽╰)╭强迫症将就看吧哈哈

调用以上方法的代码就不贴了

大概就是读取到excel中所有的搜索条件后

循环调用getLinkBySelenium,获取到需要爬取的多个url后,调用getParseredHtml2方法

----------------------割一下-----------------------------

以上,就爬取到了百度的部分数据,谢谢大家的阅读

如有问题可以直接提问,本人看到会回答的

这也是第一次分享自己的学习经验~

如果有人关注考虑贴出后面的多线程调用方法,因为phantomjs的访问速度对于需要爬取大量数据来讲实在太慢了(;´д`)ゞ

如果没人关注,就当自己的学习笔记了

如果对你有用,别忘了点赞_( ̄0 ̄)_[哦~] 告辞~~

  • 22
    点赞
  • 54
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值