基于selenium的简单的爬虫工具
一个简单的爬虫工具
前言
一般情况下,网站中的数据是以列表、分页形式展示的,这样就有了规律可循:
- 打开数据页面网址
- 定位数据列表元素
- 循环获取每个单元中所需的数据
- 定位翻页元素
- 重复2-3-4获取全部数据
那么将定位元素参数和逻辑代码进行分离,就可以达到不该变、只改变配置参数以适应符合上述规律的网站
实例
这是一个服装品牌网站信息
定位数据列表元素
定位数据元素
翻页元素定位
配置文件
<?xml version="1.0" encoding="UTF-8"?>
<!-- url:"数据所在网址路径" fileName="功能名,后续生成的Excel文件的前缀" -->
<crwal url="http://brand.efu.com.cn/list-1-9-0-0-0-0-1.html" fileName="品牌网-1">
<!--lis:"数据列表定位,结果为lsit列表" selector:"选择器类型By的方法名" value:"选择器参数" -->
<lis selector="cssSelector" value="div.lstPhotob" >
<!-- li:"每个数据定位,须唯一" name:"数据名称(索引)" type="数据所位于的元素的属性名" -->
<li selector="cssSelector" value=".lstPa-a>a" type="text" name="品牌名称"/>
<li selector="cssSelector" value=".sp-b>a" type="text" name="所属企业"/>
<!-- link:"新页面数据的链接定位" -->
<link selector="cssSelector" value=".sp-b>a">
<!-- li:"新页面数据 获取方式及定位"-->
<li selector="xpath" value="//dt[contains(text(),'地址')]/following-sibling::dd" type="text" name="企业地址"/>
<li selector="xpath" value="//dt[contains(text(),'电话')]/following-sibling::dd" type="text" name="联系电话"/>
</link>
</lis>
<!-- next:"翻页元素定位" -->
<next selector="xpath" value="//a[@class='a02' and contains(@href,'http')]"/>
</crwal>
linke标签为导向所需的新页面数据(详情)的链接元素(标签)定位
代码
四个主要类:
四个工具类:
- Apputils:接口,基于selenium、二次封装一些功能,方便调用
- Driver:浏览器驱动类,封装一些webdriver的启动参数,方便调用
- Excel:读写Excel文件类
- Utils:工具类,封装了一些简单的工具,方便直接调用
为了之后更方便运用配置文件中的数据,写了一个配置文件映射
- Mapper:映射类
public class Mapper {
//选择器
private String selector;
//选择器参数
private String value;
//自定义数据名称
private String name;
//元素的属性类型
private String type;
//元素定位构造方法
public Mapper(String selector, String value) {
super();
this.selector = selector;
this.value = value;
}
//数据定位及信息构造方法
public Mapper(String selector, String value, String name, String type) {
super();
this.selector = selector;
this.value = value;
this.name = name;
this.type = type;
}
//以下是get、set、toString方法
}
- ConfigEntity:配置解析类
public class ConfigEntity {
//配置文件
private String file ;
//根元素
private Element root;
public ConfigEntity(String file) {
this.file=file;
root();
}
//数据页面路径
public String getUrl() {
return root.attributeValue("url");
}
//功能名,生成的文件名前缀
public String getFileName() {
return root.attributeValue("fileName");
}
//翻页元素
public Mapper getNext() {
Element next = root.element("next");
return getSelectorMapper(next);
}
//数据列表定位元素
public Mapper getLis() {
Element lis = root.element("lis");
return getSelectorMapper(lis);
}
//所需数据集合
public List<Mapper> getLi(){
Element lis = root.element("lis");
return li(lis);
}
//新页面数据链接元素
public Mapper getLink() {
Element lis = root.element("lis");
Element link = lis.element("link");
if(link!=null) {
return getSelectorMapper(link);
}
return null;
}
//新页面数据集合
public List<Mapper> getLinkLi(){
Element lis = root.element("lis");
Element link = lis.element("link");
if(link!=null) {
return li(link);
}
return null;
}
//根元素
private void root() {
SAXReader read = new SAXReader();
Document doc = null;
try {
doc = read.read(new File(file));
root = doc.getRootElement();
} catch (DocumentException e) {
e.printStackTrace();
}
}
//解析数据集合,配置Mapper类型数据
private List<Mapper> li(Element lis) {
List<Mapper> list = new ArrayList<>();
List<?> li = lis.elements("li");
if(li!=null) {
for (Object el : li) {
Element e = (Element)el;
list.add(getValueMapper(e));
}
}
return list;
}
//为定位元素映射Mapper类型
private Mapper getSelectorMapper(Element el) {
return new Mapper(el.attributeValue("selector"), el.attributeValue("value"));
}
//为数据元素映射Mapper类型
private Mapper getValueMapper(Element el) {
return new Mapper(el.attributeValue("selector"), el.attributeValue("value"), el.attributeValue("name"), el.attributeValue("type"));
}
}
- Module:逻辑模块
public class Module implements AppUtils{
//配置文件路径
public static final String FILE = "src/main/resources/config/";
//浏览器驱动
private WebDriver driver;
//配置文件解析
private ConfigEntity cf;
//生成后的文件名
private String fileName;
//数据所在页面
private String url;
public Module(WebDriver driver,String configFile) {
this.driver = driver;
cf = new ConfigEntity(FILE+configFile);
this.fileName = cf.getFileName();
}
public Module(WebDriver driver,String configFile,String fileName) {
this.driver = driver;
cf = new ConfigEntity(configFile);
this.fileName = cf.getFileName()+"-"+fileName;
}
/**
* 主接口
*/
public void crawl() {
try {
//打开数据所在页面
openUrl();
//获取全部数据
List<Map<String, String>> list = all();
int size = list.size();
System.out.println("-----共"+size+"条------------");
//生成Excel文件
fileName = Excel.write(list, fileName);
//更改文件内容格式(设预览图、超链接、自动换行)
Excel.resetFormat(fileName);
System.out.println("完成!");
} catch (Exception e) {
e.printStackTrace();
}finally {
//关闭驱动
quit();
}
}
/**
* 爬取批量不同页面数据(页面路径不同,数据结构相同)
* @param urls 指定路径
*/
public void crawl(List<String> urls) {
try {
List<Map<String, String>> list = new ArrayList<Map<String,String>>();
for (String url : urls) {
setUrl(url);
openUrl();
list.addAll(all());
}
int size = list.size();
System.out.println("-----共"+size+"条------------");
fileName = Excel.write(list, fileName);
Excel.resetFormat(fileName);
System.out.println("完成!");
} catch (Exception e) {
e.printStackTrace();
}finally {
quit();
}
}
/**
* 打开数据所在页面
*/
private void openUrl() {
driver.get(cf.getUrl());
sleep(1500);
}
/**
* 获取全部数据
* @return
*/
private List<Map<String, String>> all() {
List<Map<String,String >> list =new ArrayList<>();
while (true) {
//数据列表集合
List<WebElement> lis = els(cf.getLis());
if(lis!=null) {
for (WebElement el : lis) {
//获取单条数据
Map<String, String> map = one(el);
if(map.isEmpty())continue;
list.add(map);
}
}
//翻页
WebElement next = el(cf.getNext());
if(next==null)break;
new Actions(driver).moveToElement(next).click().perform();
sleep(1500);
}
return list;
}
/**
* 获取一条数据
* @param el 每条数据所在元素
* @return
*/
private Map<String, String> one(WebElement el) {
Map<String , String> map = new LinkedHashMap<String, String>();
//获取所需数据的mappe对象集合
List<Mapper> lis = cf.getLi();
if(!lis.isEmpty()) {
for (Mapper mapper : lis) {
//设置数据
put(map, mapper,el);
}
}
//获取新页面数据
//定位连接元素
Mapper link = cf.getLink();
if(link!=null) {
//打开新页面
WebElement linkel = el(el,link);
new Actions(driver).moveToElement(linkel).click().perform();
//获取当前页面句柄
String hw = driver.getWindowHandle();
//获取所有页面句柄
Set<String> hws = driver.getWindowHandles();
for (String h : hws) {
if(!h.equals(hw)) {
//进入新页面(driver初始时已关闭全部页面。正常情况下,此时只有两个页面)
driver.switchTo().window(h);
sleep(3000);
//获取新页面数据mapper集合,设置数据内容
List<Mapper> linkLis = cf.getLinkLi();
if(linkLis!=null&&!linkLis.isEmpty()) {
for (Mapper mapper : linkLis) {
put(map, mapper,null);
}
}
//关闭新页面
driver.close();
//转回数据列表所在页面
driver.switchTo().window(hw);
}
}
}
System.out.println(map);
return map;
}
/**
* 根据mapper对象获取by对象
* @param mapper
* @return
*/
private By by(Mapper mapper) {
String selector = mapper.getSelector();
String value = mapper.getValue();
switch (selector) {
case "tagName":
return By.tagName(value);
case "className":
return By.className(value);
case "cssSelector":
return By.cssSelector(value);
case "xpath":
return By.xpath(value);
case "id":
return By.id(value);
case "linkText":
return By.linkText(value);
case "name":
return By.name(value);
case "partialLinkText":
return By.partialLinkText(value);
}
return null;
}
//以下三个方法是根据mapper对象获取元素
private WebElement el(Mapper mapper) {
return findElement(by(mapper));
}
private WebElement el(WebElement el,Mapper mapper) {
return findElement(el,by(mapper));
}
private List<WebElement> els(Mapper mapper) {
return findElements(by(mapper));
}
/**
* 向map中添加数据
* @param map map对象
* @param mapper mapper对象
* @param e 定位到的数据元素
*/
private void put(Map<String, String> map,Mapper mapper,WebElement e) {
WebElement el = e==null? el(mapper) : el(e,mapper);
//获取数据名称,设置map的key
String name = mapper.getName();
String value = "";
if(el!=null) {
//根据数据所位于元素中的属性类型获取数据
String type = mapper.getType();
if(type.equals("text")) {
value = el.getText().trim();
int index = value.indexOf(":")>=0? value.indexOf(":") : value.indexOf(":");
if(index==0) {
value = value.substring(1);
}else if(index>=1&&index==value.length()-1) {
value = value.substring(0, index);
}
}else {
value = el.getAttribute(type).trim();
if(value.startsWith("//"))value = "http:"+value;
}
}
//map对象添加键值队
map.put(name, value);
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = cf.getFileName()+"-"+fileName;
}
public ConfigEntity getCf() {
return cf;
}
@Override
public WebDriver getDriver() {
return driver;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
}
- Main:启动类
public class Main {
public static void main(String[] args) {
String FILE = "config.xml";
//创建模块Module对象(这里用的是远程debug模式连接已经打开的浏览器,有些需要登录的网站,可以先登录再连接)
Module m = new Module(Driver.buildChromeDriver(9999),FILE,"女装");
try {
//执行爬虫方法
m.crawl();
} catch (Exception e) {
e.printStackTrace();
}finally {
//关闭当前程序
System.exit(0);
}
}
public static void run360() {
Runtime run = Runtime.getRuntime();
try {
//run.exec("taskkill /f /fi \"imagename eq 360se.exe\" /t");
run.exec("D:\\360\\360se6\\Application\\360se.exe --remote-debugging-port=9999");
} catch (IOException e) {
e.printStackTrace();
}
}
}