新手小白入手selenium+chromedriver爬虫,爬取各种网站之后觉得只要能看到的都能抓到是真方便,就是效率低了点。所以开始加点东西提高一下爬虫效率。对我来说最直接的方法就是单线程变多线程~~~
1、webmagic爬取数据 规则
框架 |
Selenium |
webmagic |
抓取规则 |
针对单个或者一类页面制定爬虫规则 |
针对多类页面制定多种爬虫规则垂直爬取 |
线程 |
单线程 |
多线程 |
解析json |
需要其他jar辅助 |
内置json解析工具 |
页面抽取工具 |
内置页面抽取规则 |
内置页面抽取规则 |
断电重续 |
不存在 |
存在 |
IP代理池 |
需要自己写 |
0.4版本之后开始出现内置代理池(性能不稳定),0.6版本之后能够自己编写IP代理池 |
以往的爬虫当中需要针对某一个或者一类页面单独制定爬虫规则,webmagic也是如此,不同的是webmagic是垂直爬取。
什么是垂直爬取呢?来看个图:
这是树形图与webmagic的抓取逻辑类似,我们可以把“语法树根节点”理解为我们抓取的起始页面,在这个页面我们除了可以抓取需要的数据,还能获得子页面的链接(if语句、调用方法),我们将子页面的链接加入待抓取队列,那么我们接下来就会对子页面当中的信息进行抓取,依次类推我们可以获得不同深度(主页面当中的子页面深度为1,以此类推)页面当中的数据,同时能够不断的在抓取队列当中添加信息。
我们在抓取队列当中不断添加需要抓取的页面链接,但是各个深度的页面抓取规则和需要的数据也是不一样的,按照以往我们需要写很多的程序,webmagic通过Page对象解决这一个问题,我们在Page对象当中对抓取页面进行分类,然后再匹配对应的抓取规则。
2、webmagic框架搭建
2.1 mavan搭建
us.codecraft
webmagic-core
0.7.3
us.codecraft
webmagic-extension
0.7.3
2.2 jar包搭建
需要的jar太多了去我的百度网盘下载吧(提取码:p5z6)
3、创建爬虫项目
做爬虫总是要寻找示例的,webmagic适合爬取含有至少两层深度的数据源,或者是含有众多子页面的数据源。
最近在整理数据源的盘口数据那么我就拿其中一个作为爬取示例。
首先分享一下目录结构(web工程)
别的不多说了直接上代码
package cyt.selenium;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import pankou.dao.ALLcharDao;
import pankou.pojo.AllChar;
import us.codecraft.webmagic.Page;
import us.codecraft.webmagic.Site;
import us.codecraft.webmagic.Spider;
import us.codecraft.webmagic.pipeline.ConsolePipeline;
import us.codecraft.webmagic.processor.PageProcessor;
public class seleniumXxdj implements PageProcessor {
/**
* vpgame盘口数据抓取小程序
* @date 2018-8-20
* @website http://www.vpgame.com/###
* @author jingsheng
* @game lol
*/
// 抓取网站的相关配置,包括编码、抓取间隔、重试次数等
private Site site = Site.me().setRetryTimes(10).setSleepTime(1000).addHeader("Accept-Encoding", "/");
private static int count =0;
public void process(Page page) {
//加载webdriver驱动
System.setProperty("webdriver.chrome.driver", "D:/cyt_down/selenium/chromedriver.exe");
WebDriver driver = new ChromeDriver();
String nowUrlStr = page.getUrl().toString() ;
if(nowUrlStr.indexOf("game")!=-1){
driver.get(nowUrlStr);
try {
Thread.sleep(3000);
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
int matchNumber = driver.findElements(By.className("match")).size();
System.out.println("目前一共有 " + matchNumber + "条数据。准备进行筛选~~~~");
for(int a=0;a2){
System.out.println("比赛正在进行中");
trueNum = 0 ;
}
try {
gameStatetest = matchList.findElement(By.xpath("./div["+(1+trueNum)+"]")).getText();
System.out.println("gameStatetest" + gameStatetest);
} catch (Exception e) {
System.err.println("gameStatetest 不存在");
continue;
}
try {
url = matchList.findElement(By.xpath("./div["+(6+trueNum)+"]/ul/li[2]/a")).getAttribute("href");
System.out.println("url" + url);
} catch (Exception e) {
System.err.println("url 不存在");
e.printStackTrace();
continue;
}
try {
matchCLass = matchList.findElement(By.xpath("./div["+(6+trueNum)+"]/ul/li[2]/a")).getAttribute("class");
System.out.println("matchCLass " + matchCLass);
} catch (Exception e) {
System.out.println("matchCLass 不存在");
}
try {
String gameStateStr_test = matchList.findElement(By.xpath("./div["+(7+trueNum)+"]")).getText();
gameStateStr = judgmentGameStateStr(gameStateStr_test);
itemStaus = judgmentItemStaus(gameStateStr_test);
System.out.println("gameStateStr " + gameStateStr);
} catch (Exception e) {
System.out.println("gameStateStr 不存在");
}
try {
LeagueName = matchList.findElement(By.xpath("./div["+(5+trueNum)+"]/div")).getText().replaceAll("\\s*", "");
System.out.println("LeagueName " + LeagueName);
} catch (Exception e) {
System.out.println("LeagueName 不存在");
}
try {
VisitPic = matchList.findElement(By.xpath("./div["+(4+trueNum)+"]/div/span[1]/img")).getAttribute("src");
System.out.println("VisitPic " + VisitPic);
} catch (Exception e) {
System.out.println("VisitPic 不存在");
}
try {
VisitName = matchList.findElement(By.xpath("./div["+(4+trueNum)+"]/div/span[2]")).getText();
System.out.println("VisitName " + VisitName);
} catch (Exception e) {
System.out.println("VisitName 不存在");
}
try {
HomeScore = matchList.findElement(By.xpath("./div["+(2+trueNum)+"]/div[2]")).getText();
System.out.println("HomeScore " + HomeScore);
} catch (Exception e) {
System.out.println("HomeScore 不存在");
}
try {
VisitScore = matchList.findElement(By.xpath("./div["+(4+trueNum)+"]/span")).getText();
System.out.println("VisitScore " + VisitScore);
} catch (Exception e) {
System.out.println("VisitScore 不存在");
}
try {
HomeName = matchList.findElement(By.xpath("./div["+(2+trueNum)+"]/div[1]/span[1]")).getText();
System.out.println("HomeName " + HomeName);
} catch (Exception e) {
System.out.println("HomeName 不存在");
}
count = count +1 ;
} catch (Exception e) {
e.printStackTrace();
}
}
}else if(nowUrlStr.indexOf("match")!=-1) {
//抓取详情页信息
driver.get(nowUrlStr);
String startTime = ""; String fullScore= "";
boolean next = false;
//1、判断是否存在盘口数据
try {
String pankouStr = driver.findElement(By.className("matchRight")).findElement(By.xpath("./div[1]/p")).getText();
next = true ;
} catch (Exception e) {
System.err.println("不存在盘口数据");
}
//2、遍历盘口数据,进行存储
if(next==true){
try {
String test = driver.findElement(By.xpath("/html/body/div[1]/div[2]/div/div[1]/div[1]/div[3]/p")).getText();
startTime = judgmentStartTime(test);
fullScore = driver.findElement(By.xpath("/html/body/div[1]/div[2]/div/div[1]/div[1]/div[3]/div[1]/span")).getText();
WebElement matchRight = driver.findElement(By.className("matchRight"));
List list = driver.findElement(By.className("matchRight"))
.findElement(By.xpath("./div[1]"))
.findElements(By.tagName("dl"));
System.out.println("目前一共拥有 " + list.size() +"条数据");
for(int a=0;a");
new ALLcharDao().updateXx(allChar);
System.out.println(" 更新成功");
}else{
new ALLcharDao().add(allChar);
System.out.println("盘口数据改变,更新");
}
}
//把对象输出到控制台
System.out.println(allChar);
count = count +1 ;
}
} catch (Exception e) {
System.err.println("盘口数据出错");
e.printStackTrace();
}
}else{
String test = driver.findElement(By.xpath("/html/body/div[1]/div[2]/div/div[1]/div[1]/div[3]/p")).getText();
startTime = judgmentStartTime(test);
fullScore = driver.findElement(By.xpath("/html/body/div[1]/div[2]/div/div[1]/div[1]/div[3]/div[1]/span")).getText();
count = count +1 ;
}
}
driver.quit();
}
public Site getSite() {
return this.site;
}
@SuppressWarnings("deprecation")
public static void main(String[] args) {
long startTime, endTime;
System.out.println("开始爬取...");
startTime = System.currentTimeMillis();
System.out.println(startTime);
List startUrls =new ArrayList();
startUrls.add("https://www.xxdianjing.com/game11.html");
startUrls.add("https://www.xxdianjing.com/game24.html");
startUrls.add("https://www.xxdianjing.com/game65.html");
startUrls.add("https://www.xxdianjing.com/game254.html");
startUrls.add("https://www.xxdianjing.com/game205.html");
Spider.create(new seleniumXxdj())
.startUrls(startUrls)
.thread(5)
.addPipeline(new ConsolePipeline())
.run();
endTime = System.currentTimeMillis();
System.out.println("爬取结束,耗时约" + ((endTime - startTime) / 1000) + "秒,抓取了"+count+"条记录");
}
public static String judgmentCnName (String str){
String newStr = "" ;
if(str.indexOf("game11.html")!=-1){
return newStr= "英雄联盟";
}else if(str.indexOf("game24.html")!=-1){
return newStr= "刀塔2";
}else if(str.indexOf("game65.html")!=-1){
return newStr= "守望先锋";
}else if(str.indexOf("game254.html")!=-1){
return newStr= "王者荣耀";
}else if(str.indexOf("game205.html")!=-1){
return newStr= "反恐精英";
}else{
return newStr ;
}
}
public static String judgmentEnName (String str){
String newStr = "" ;
if(str.indexOf("game11.html")!=-1){
return newStr= "League of Legends";
}else if(str.indexOf("game24.html")!=-1){
return newStr= "Dota2";
}else if(str.indexOf("game65.html")!=-1){
return newStr= "OW";
}else if(str.indexOf("game254.html")!=-1){
return newStr= "King of Glory";
}else if(str.indexOf("game205.html")!=-1){
return newStr= "CSGO";
}else{
return newStr ;
}
}
public static String judgmentGameId (String str){
String newStr = "" ;
if(str.indexOf("game11.html")!=-1){
return newStr= "002";
}else if(str.indexOf("game24.html")!=-1){
return newStr= "003";
}else if(str.indexOf("game65.html")!=-1){
return newStr= "006";
}else if(str.indexOf("game254.html")!=-1){
return newStr= "001";
}else if(str.indexOf("game205.html")!=-1){
return newStr= "004";
}else{
return newStr ;
}
}
public static String judgmentGameState (String str){
String newStr = "" ;
if(str.indexOf("进行中")!=-1){
return newStr= "1";
}else{
return newStr= "0" ;
}
}
public static String judgmentGameStateStr (String str){
String newStr = "" ;
if(str.indexOf("距竞猜截止还有")!=-1) {
return newStr= "比赛未开始" ;
}else if(str.indexOf("暂无竞猜或竞猜未开始")!=-1) {
return newStr= "比赛未开始" ;
}else if(str.indexOf("竞猜截止时间已到")!=-1) {
return newStr= "比赛进行中" ;
}else {
return newStr= "" ;
}
}
public static String judgmentItemStaus (String str){
String newStr = "" ;
if(str.indexOf("进行中")!=-1){
return newStr= "已结束";
}else{
return newStr= "竞猜中" ;
}
}
public static String judgmentStartTime (String str){
try {
String newStr = str.split(" ")[0] +" " + str.split(" ")[1];
return newStr ;
} catch (Exception e) {
return "" ;
}
}
}
4、程序分析
webmagic结合selenium确实似的爬虫效率大大提升,可是也让潜在问题爆发出来,似的一些东西成为必不可少的,就比如说IP代理池和网站反爬虫机制(极验验证、验证码、页面数据加密)。
webmagic从0.4.0版本开始,支持Http代理。因为场景的多样性,代理这部分的API一直处于不稳定状态,但是因为需求确实存在,所以webmagic会继续支持代理部分的完善。在0.6.0版本后,允许实现自己的代理池,通过扩展接口ProxyPool来实现。目前webmagic的代理池逻辑是:轮流使用代理池中的IP,如果某个IP失败超过20次则增加两小时的重用时间,具体实现可以参考SimpleProxyPool。
这里我给一下添加IP代理的方法,不过由衷感叹自己抓的代理池真心不好用
//添加单个IP
site.setHttpProxy(new HttpHost("101.101.101.101",8888))
.setUsernamePasswordCredentials(new UsernamePasswordCredentials("username","password"))
//添加多个IP
List poolHosts = new ArrayList();
poolHosts.add(new String[]{"username","password","101.101.101.101","8888"});
poolHosts.add(new String[]{"username","password","102.102.102.102","8888"});
//httpProxyList输入是IP+PORT, isUseLastProxy是指重启时是否使用上一次的代理配置
site.setHttpProxyPool(poolHosts,false);
极验验证说简单也是简单,说难也是难。说一下我处理极验验证的方法。
极验验证一半来说是在页面当中存在20张Img,这20张Img按照一定的顺序能够拼接成页面当中显示的图片,不过这个图片在当中是缺少一块的,我们根据拼接成的图片找到当中颜色与其他地方不一样的一块,然后根据这个块的大小计算,我们需要移动页面当中小块需要移动的距离。
在实际当中往往我们需要考虑更多的东西,页面当中小块移动的距离,小块完成移动需要的时间,针对同一滑块我们往往为了掩饰机器,需要放慢速度,多设计几种滑块速度。