由于最近业务需要爬取第三方网站的数据,开始学习JAVA爬虫的相关知识,在此记录期间遇到的问题以及对知识进行总结。
爬虫开始
打开浏览器进入目标网站,按F12打开控制台。
1. HTTP
如果能在控制台里直接找到数据接口,那么这是最好的爬取数据方式。点击network,现在大多数网站都是异步请求,所以筛选xhr请求,有的网站在点击分页时会暴露xhr请求需要多试试。例如[https://www.iqiyi.com/r_bodan...]点击分页后可以看到异步请求的url。找到需要的接口后:
(1)使用okhttp/httpclient等工具请求接口。
(2)JSONObject解析返回的json。
/**
* OkHttpGet请求封装
*/
public static String okHttpGet(String url,int timeOut)throws IOException{
String result = "";
OkHttpClient client = new OkHttpClient.Builder()
.followRedirects(false)
.followSslRedirects(false)
.connectTimeout(timeOut, TimeUnit.SECONDS)
.build();
Request request = new Request.Builder()
.url(url)
.get()
.build();
final Call call = client.newCall(request);
try {
Response response = call.execute();
return response.body().string();
} catch (IOException e) {
logger.error("n okHttp IOException nurl: {}",url);
throw new IOException();
}
}
2. Jsoup
找不到数据接口后,可以用HTTP工具请求目标页获取网页的源码。这时候需要另一个工具JSoup。Jsoup是一款Java的HTML解析器,主要用来对HTML解析。官方文档:https://www.open-open.com/jsoup/
Document doc = Jsoup.connect(url)
.cookies(cookieMap)
.header("referer", "https://acz.youku.com/wow/ykpage/act/top_hot?spm=a2ha1.14919748_WEBHOME_GRAY.youku-search-box.5~5~A")
.timeout(0).get();
doc里的内容就是网页的源码,timeout是等待时间,当超过等待时间时请求就会中断,0表示一直等待。
String imgUrl = doc.select("div.poster-img_2PAaJ").attr("style");
像这样就可以获取class为'poster-img_2PAaJ'的div的style的内容,Jsoup有丰富的解析元素的方法,具体可以查看上述的官方文档。
值得注意的是,有很多网站把源代码的数据放在了script标签里面,还是json格式,如果能把数据源的json提取出来再用JSONObject解析不就快多了嘛。
Element scriptElement = doc.getElementsByTag("script").get(4);
String[] scriptDataArray = scriptElement.data().split("var");
String[] videoInfo = scriptDataArray[5].split("= ");
JSONObject videoJsonObject = JSONObject.parseObject(videoInfo[1]);
// 就是用String的split()方法切割出json
// 实际使用时需要防范数组越界问题
Jsoup缺点:并不支持xpath解析。
3. Selenium PhantomJS
以上的两种方法基本可以应对很多网站的爬虫,但是有些网站对爬虫的防范很高,比如:
网页源代码里没有相关数据,用Jsoup也获取不到主页面的元素。这时候就需要Selenium和PhantomJS出场了。
Selenium是一个用于Web应用程序测试的工具,它可以模拟真实的浏览器并模拟用户在操作,它不仅可以用于应用程序测试,在爬虫方面也可以爬取js渲染的网站。
PhantomJS与Selenium的区别主要在于它是一种无界面的浏览器,由于省去了界面加载所以执行速度相比较快,但是由于phantomJS已经停止了更新,并且在JAVA版本中的PhantomJS无法执行JS脚本(比如模拟滚动条滚动),所以不会优先考虑。
引入依赖
org.seleniumhq.selenium
selenium-java
3.141.59
Selenium支持许多浏览器驱动比如IE,Chrome,Firefox,看别的博客说火狐支持的最好,那就选火狐吧。
下载geckodriver驱动,并安装firefox,都选最新的就完事。
https://github.com/mozilla/geckodriver
程序自动打开一个火狐浏览器并自动访问目标网页。
DesiredCapabilities desiredCapabilities = new DesiredCapabilities();
//ssl证书支持
desiredCapabilities.setCapability("acceptSslCerts", true);
//css搜索支持
desiredCapabilities.setCapability("cssSelectorsEnabled", true);
//js支持
desiredCapabilities.setJavascriptEnabled(true);
System.setProperty("webdriver.gecko.driver", "D:\\geckodriver-v0.27.0-win64\\geckodriver.exe");
WebDriver driver = new FirefoxDriver();
String href = "https://www.ixigua.com/cinema/filter/dianshiju/?sort=%E6%9C%80%E7%83%AD";
driver.get(href); //访问目标网站
List element = driver.findElements(By.className("lvideo-list__item"))
// 获取元素,类似于Jsoup的元素解析
还可以模拟用户操作,例如点击class名为'a'的元素
Actions action = new Actions(driver);
WebElement element = driver.findElement(By.className("a"));
action.click(element).perform
对于一些滑动加载的页面,可以模拟页面滚动。
WebDriver driver = HttpClientUtil.getFireFoxDriver(false, null); // 包装了一下实例化代码
driver.get(url)
List listItem;
int j = 0;
// 滚动页面直到加载了100个'lvideo-list__item'元素
while (true) {
((JavascriptExecutor) driver).executeScript("window.scrollTo(0," + (j * 500) + ")");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
listItem = driver.findElements(By.className("lvideo-list__item"));
if (listItem.size() >= 120 || listItem.size() == 0) {
break;
}
j++;
}
当我心满意足的在自己的电脑上爬完数据后,准备放到测试服务器运行,哦豁报错,原来是由于linux没有图形化界面启动不了,一种方法是用phantomJS,但是呢偏偏不维护了而且有bug,后来才发现selenium现在也支持无头(无界面)模式了
FirefoxOptions firefoxOptions = new FirefoxOptions();
firefoxOptions.setHeadless(true); //设置为无头模式
WebDriver driver = new FirefoxDriver(firefoxOptions);
driver.manage().window().maximize();
这样就没问题了。
值得一提的是,driver有两种关闭,driver.close()和driver.quit()两个方法,close()方法意思是关闭当前tab,假设打开了5个tab执行close只会关闭当前tab还剩下4个,当只有一个tab时执行close会关闭浏览器,但这个时候打开进程管理器发现进程还在,所以在最后释放资源的时候用driver.quit()方法,它会直接关闭进程。
}finally{
driver.quit();
}