jsoup 是一款Java 的HTML解析器,可直接解析某个URL地址、HTML文本内容。它提供了一套非常省力的API,可通过DOM,CSS以及类似于jQuery的操作方法来取出和操作数据。
org.jsoup.Jsoup类
方法 | 描述 |
---|---|
static Connection connect(String url) | 创建并返回URL的连接。 |
static Document parse(File in, String charsetName) | 将指定的字符集文件解析成文档。 |
static Document parse(String html) | 将给定的html代码解析成文档。 |
static String clean(String bodyHtml, Whitelist whitelist) | 从输入HTML返回安全的HTML,通过解析输入HTML并通过允许的标签和属性的白名单进行过滤。 |
org.jsoup.nodes.Document类
修饰符和类型 | 方法 | 描述 |
---|---|---|
Connection | connection() | 返回用于获取此文档的连接(请求/响应)对象(如果有);否则,一个新的默认 Connection 对象。 |
Document | connection(Connection connection) | 设置用于获取此文档的连接。 |
org.jsoup.nodes.Element类
修饰符和类型 | 方法 | 描述 |
---|---|---|
Element | child(int index) | 通过从 0 开始的索引号获取此元素的子元素。 |
int | childNodeSize() | 获取该节点拥有的子节点数。 |
Elements | children() | 获取此元素的子元素。 |
int | childrenSize() | 获取此元素的子节点的数量。 |
String | className() | 获取此元素的“类”属性的字面值,其中可能包含多个类名,以空格分隔。 |
Set | classNames() | 获取所有元素的类名。 |
String | cssSelector() | 获取将唯一选择此元素的 CSS 选择器。 |
String | data() | 获取该元素的组合数据。 |
Elements | getAllElements() | 查找此元素下的所有元素(包括 self 和 children 的 children)。 |
Element | getElementById(String id) | 按 ID 查找元素,包括或在此元素下。 |
Elements | getElementsByAttribute(String key) | 查找具有命名属性集的元素。 |
Elements | getElementsByAttributeStarting(String keyPrefix) | 查找属性名称以提供的前缀开头的元素。 |
Elements | getElementsByAttributeValue(String key, String value) | 查找具有特定值的属性的元素。 |
Elements | getElementsByAttributeValueContaining(String key, String match) | 查找具有其值包含匹配字符串的属性的元素。 |
Elements | getElementsByAttributeValueEnding(String key, String valueSuffix) | 查找具有以值后缀结尾的属性的元素。 |
Elements | getElementsByAttributeValueMatching(String key, String regex) | 查找具有其值与提供的正则表达式匹配的属性的元素。 |
Elements | getElementsByAttributeValueMatching(String key, Pattern pattern) | 查找具有其值与提供的正则表达式匹配的属性的元素。 |
Elements | getElementsByAttributeValueNot(String key, String value) | 查找不具有此属性或具有不同值的元素。 |
Elements | getElementsByAttributeValueStarting(String key, String valuePrefix) | 查找具有以值前缀开头的属性的元素。 |
Elements | getElementsByClass(String className) | 查找具有此类的元素,包括或在此元素下。 |
Elements | getElementsByIndexEquals(int index) | 查找兄弟索引等于提供的索引的元素。 |
Elements | getElementsByIndexGreaterThan(int index) | 查找兄弟索引大于提供的索引的元素。 |
Elements | getElementsByIndexLessThan(int index) | 查找同级索引小于提供的索引的元素。 |
Elements | getElementsByTag(String tagName) | 查找具有指定标记名称的元素,包括并递归地在此元素下。 |
Elements | getElementsContainingOwnText(String searchText) | 查找直接包含指定字符串的元素。 |
Elements | getElementsContainingText(String searchText) | 查找包含指定字符串的元素。 |
Elements | getElementsMatchingOwnText(String regex) | 查找其自身文本与提供的正则表达式匹配的元素。 |
Elements | getElementsMatchingOwnText(Pattern pattern) | 查找其自身文本与提供的正则表达式匹配的元素。 |
Elements | getElementsMatchingText(String regex) | 查找其文本与提供的正则表达式匹配的元素。 |
Elements | getElementsMatchingText(Pattern pattern) | 查找其文本与提供的正则表达式匹配的元素。 |
Elements | select(String cssQuery) | 查找与SelectorCSS 查询匹配的元素,并以该元素作为起始上下文。 |
// 方式一
Document document = Jsoup.connect("http://www.baidu.com/").get();
// 方式二
Document parse = Jsoup.parse(new URL("http://www.baidu.com/"), 3000);//3秒
Document doc = Jsoup.connect("http://example.com")
.data("query", "Java")
.userAgent("Mozilla")
.cookie("auth", "token")
.timeout(3000)
.post();
getElementById(String id):通过id来获取
getElementsByTag(String tagName):通过标签名字来获取
getElementsByClass(String className):通过类名来获取
getElementsByAttribute(String key):通过属性名字来获取
getElementsByAttributeValue(String key, String value):通过指定的属性名字,属性值来获取
getAllElements():获取所有元素
File input = new File("/tmp/input.html");
Document doc = Jsoup.parse(input, "UTF-8", "http://example.com/");
Elements links = doc.select("a[href]"); //带有href属性的a元素
Elements pngs = doc.select("img[src$=.png]");
//扩展名为.png的图片
Element masthead = doc.select("div.masthead").first();
//class等于masthead的div标签
Elements resultLinks = doc.select("h3.r > a"); //在h3元素之后的a元素
Document documents = Jsoup.parse(new URL(url),3000);
Elements images = document.select("img[src~=(?i)\\.(png|jpe?g|gif)]");
for (Element image : images)
{
System.out.println("src : " + image.attr("src"));
System.out.println("height : " + image.attr("height"));
System.out.println("width : " + image.attr("width"));
System.out.println("alt : " + image.attr("alt"));
}
package test;
import entity.Commodity;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.io.IOException;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
/**
* 获取商品信息
* @author 有多勉为其难
* @version 1.0.0
* @date 2022/9/6
*/
public class HtmlParseUtils {
public static void main(String[] args) throws Exception {
new HtmlParseUtils().getCommodity("鞋子", 10000).forEach(System.out::println);
}
private List<Commodity> getCommodity(String keyword, int timeout) throws IOException {
//设置url编码
String keywords = URLEncoder.encode(keyword,"utf-8");
String url = "https://search.jd.com/Search?keyword="+keywords;
Document document = Jsoup.parse(new URL(url), timeout);
Element element = document.getElementById("J_goodsList");
Elements liElements = element.getElementsByTag("li");
List<Commodity> commodityList = new ArrayList<>();
for (Element el : liElements) {
String img = el.getElementsByTag("img").eq(0).attr("data-lazy-img").trim();
String price = el.getElementsByClass("p-price").eq(0).text().trim();
String name = el.getElementsByClass("p-name").eq(0).text().trim();
Commodity commodity = new Commodity();
commodity.setName(name);
commodity.setImg(img);
commodity.setPrice(price);
commodityList.add(commodity);
}
return commodityList;
}
}
package entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 商品实体类
* @author 有多勉为其难
* @version 1.0.0
* @date 2022/9/6
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Commodity {
private String name;
private String img;
private String price;
}
package test;
import entity.Image;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import utils.DownloadUtils;
import java.io.IOException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
/**
* 找到并下载图片
*
* @author 有多勉为其难
* @date 2022/9/6
*/
public class DownloadImagesUtils {
public static void main(String[] args) throws IOException {
String url = "https://www.quanjing.com/creative/topic/1";
downloadImage(url, "a");
}
private static void downloadImage(String url, String keyword) throws IOException {
//设置url编码
String keywords = URLEncoder.encode(keyword, "utf-8");
Document doc = Jsoup.parse(new URL(url + keywords), 3000);
Element element = doc.getElementById("gallery-list");
Elements elements = element.getElementsByTag("li");
List<Image> imageList = new ArrayList<>();
List<String> urlList = new ArrayList<>();
for (Element imgElement : elements) {
String src = imgElement.getElementsByTag("img").eq(0).attr("src");
String alt = imgElement.getElementsByTag("alt").eq(0).attr("alt");
Image image = new Image();
image.setAlt(alt);
image.setSrc(src);
imageList.add(image);
urlList.add(src);
}
System.out.println(imageList);
System.out.println(urlList);
DownloadUtils.processSync(urlList, keyword);
}
}
package utils;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 图片下载工具类
*
* @author 有多勉为其难
* @version 1.0.0
* @date 2022/9/6
*/
public class DownloadUtils {
private String extension = ".jpg";
private String path;
private static volatile AtomicInteger suc;
private static volatile AtomicInteger fails;
public DownloadUtils() {
setPath("E:/pipeline/");
suc = new AtomicInteger();
fails = new AtomicInteger();
}
public void setPath(String path) {
this.path = path;
}
/**
* 下载
*
* @param url
* @param cate
* @throws Exception
*/
private void downloadImg(String url, String cate, String name) throws Exception {
String path = this.path + "/" + cate + "/";
File dir = new File(path);
if (!dir.exists()) { // 目录不存在则创建目录
dir.mkdirs();
}
// 获取扩展名
String realExt = url.substring(url.lastIndexOf("."));
String fileName = name + realExt;
fileName = fileName.replace("-", "");
String filePath = path + fileName;
File img = new File(filePath);
// 若文件之前已经下载过,则跳过
if (img.exists()) {
System.out.println(String.format("文件%s已存在本地目录", fileName));
return;
}
URLConnection con = new URL(url).openConnection();
con.setConnectTimeout(5000);
con.setReadTimeout(5000);
InputStream inputStream = con.getInputStream();
byte[] bs = new byte[1024];
File file = new File(filePath);
FileOutputStream os = new FileOutputStream(file, true);
// 开始读取 写入
int len;
while ((len = inputStream.read(bs)) != -1) {
os.write(bs, 0, len);
}
System.out.println("picUrl: " + url);
System.out.println(String.format("正在下载第%s张图片", suc.getAndIncrement()));
}
/**
* 多线程处理
*
* @param data
* @param word
*/
public static void processSync(List<String> data, String word) {
long start = System.currentTimeMillis();
int count = 0;
DownloadUtils downloadUtils = new DownloadUtils();
// 创建缓存线程池
/*
优点:线程池会根据任务数量创建线程池,并且在一定时间内可以重复使用这些线程,产生相应的线程池。
缺点:适用于短时间有大量任务的场景,它的缺点是可能会占用很多的资源。
*/
ExecutorService executorService = Executors.newCachedThreadPool();
// ThreadPoolExecutor executorService = new ThreadPoolExecutor(5, 10, 10, TimeUnit.SECONDS, new ArrayBlockingQueue<>(100));
for (int i = 0; i < data.size(); i++) {
String picUrl = data.get(i);
if (picUrl == null) {
continue;
}
String name = "";
if (i < 10) {
name = "000" + i;
} else if (i < 100) {
name = "00" + i;
} else if (i < 1000) {
name = "0" + i;
}
String finalName = name;
executorService.execute(() -> {
try {
downloadUtils.downloadImg(picUrl, word, finalName);
} catch (Exception e) {
// e.printStackTrace();
DownloadUtils.fails.incrementAndGet();
}
});
count++;
}
executorService.shutdown();
try {
if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
// 超时的时候向线程池中所有的线程发出中断(interrupted)。
// executorService.shutdownNow();
}
System.out.println("AwaitTermination Finished");
System.out.println("共有URL: " + data.size());
System.out.println("下载成功: " + suc);
System.out.println("下载失败: " + fails);
File dir = new File(downloadUtils.path + "/" + word + "/");
int len = Objects.requireNonNull(dir.list()).length;
System.out.println("当前共有文件: " + len);
long end = System.currentTimeMillis();
System.out.println("耗时:" + (end - start) / 1000.0 + "秒");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@PostConstruct是Java自带的注解,在方法上加该注解会在项目启动的时候执行该方法,也可以理解为在spring容器初始化的时候执行该方法。
@SpringBootApplication
@ComponentScan(basePackages = {"com.my.spider"})
@MapperScan("com.my.spider.mapper")
public class MySpiderApplication {
@Autowired
private SpiderHandle spiderHandle;
@Autowired
private ContentNoticeHandle contentNoticeHandle;
public static void main(String[] args) {
SpringApplication.run(MySpiderApplication.class, args);
}
@PostConstruct
public void task(){
contentNoticeHandle.spiderData();
}/*
@PostConstruct
public void task(){
spiderHandle.spiderData();
}
*/
}