Jsoup线程池分页爬取IP
最近写个邮件群发工具,要设置代理发送,然后就写了这个玩意。。。。。。
- 爬取目标:免费代理ip_服务器http代理_最新ip代理_免费ip提取网站_国内外代理_66免费代理ip (66ip.cn)
- 准备好创建线程池 和 IP对象
- 解析页面获取数据,并将数据存入到集合中
- 遍历测试IP是否可用
- 通过ObjectOutputStream将ip存集合作为对象存入到文件中
- 通过ObjectInputStream读取ip集合对象文件
1、准备好创建连接池工具类 和 IP对象(不用这个线程池也无所谓)
线程池:
/**
* 线程池工具类
*/
public class PoolSend {
BlockingQueue<Runnable> workQueue;//任务队列
ExecutorService es;//线程池的接口
/**
* @param corePoolSize 初始线程
* @param maximumPoolSize 最大线程
*/
public PoolSend(Integer corePoolSize,Integer maximumPoolSize) {
startTime = System.currentTimeMillis();
workQueue = new LinkedBlockingQueue<>();//构造无界的任务队列,资源足够,理论可以支持无线个任务
es = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, //2 core; 4 max
3000, TimeUnit.SECONDS, workQueue, //30s keep alive
new ThreadPoolExecutor.CallerRunsPolicy()); //任务失败重试
}
public void send(Runnable task) {
System.out.println("PoolSend start sending mail...");
es.execute(task);//将任务放入线程池
}
public void close() {// 关闭
es.shutdown();
while (true) {//让主线程等待所有任务都执行结束
if (es.isTerminated()) {//所有的子线程都结束了
break;
}
}
}
}
IP对象,这个ip对象是我数据库中生成出来的bean,我们只需要往里面存IP和端口就行了,其他子网掩码、ip编号不用管:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Ipconfig implements Serializable {
private static final long serialVersionUID = -60500851059312504L;
/**
* IP编号
*/
private Integer id;
/**
* IP地址
*/
private String ip;
/**
* 子网掩码
*/
private String dns;
/**
* 端口
*/
private String port;
public Ipconfig(String ip, String port) {
this.ip = ip;
this.port = port;
}
}
2、解析页面获取数据
对于这个代理ip网站的数据爬虫我写了两个方法:
- 一个是爬取该网站的所有IP
/**
* 爬取该网站所有免费提供的代理IP 并将IP存入到文件中
*
* @return 返回IP集合
* @throws Exception
*/
public static List crawl() throws Exception {
Integer index = 0;//每个线程爬取的起始页
Integer index2 = 50;//每个爬取的结束页
//这个集合就是我们所要返回的对象了
ArrayList ipConfigList = new ArrayList<Ipconfig>();
//这个是干嘛的?偶偶,这个就是用来搞一下随机线程睡眠时间,不设置也无所谓
Random random = new Random();
/*创建线程池*/
PoolSend poolSend = new PoolSend(46, 46);
/*使用for循环开启46个线程,每个线程爬取50个页面,一共爬取2300页面的IP*/
for (int i = 0; i < 46; i++) {
Integer finalIndex = index;
Integer finalIndex1 = index2;
//使用线程池
poolSend.send(() -> {
/*要返回的对象*/
for (int j = finalIndex; j <= finalIndex1; j++) {
try {
URL url = null;
if (j == 1) { //是第一页的时候
url = new URL("http://www.66ip.cn/index.html");
} else {
url = new URL("http://www.66ip.cn/" + j + ".html");
}
//随机一个休眠时间
Long s = (long) (random.nextInt(5000 - 1000 + 1) + 1000);
//当前线程休眠,休眠过后就开始解析网页
Thread.currentThread().sleep(s);
/*传入爬取的url和超时等待时间*/
Document document = Jsoup.parse(url, 1000000);
//通过css选择器来获取对应的标签
Elements table = document.select("table tr:not(tr:nth-child(1))");
//遍历获取IP地址
for (Element element : table) {
//ip
String ip = element.child(0).text();
//端口
String port = element.child(1).text();
//然后将ip和port封装成我们前面的那个对象
Ipconfig ipconfig = new Ipconfig(ip, port);
/**
* isValid?啊你tm这个方法是个啥?
* 这个方法是用来判断该ip是否可用的,可以看下面文章,有给出代码
* 返回true就表示这个ip得行,放心用
* 返回false的话。。。懂得都懂
*/
boolean valid = isValid(ipconfig);
if (valid) {
//可以用的ip就放入我们要返回的对象中
ipConfigList.add(ipconfig);
System.out.println("====爬取成功" + ipConfigList.size());
} else {
System.out.println("很遗憾,这个ip不合格:" + ipconfig.getIp() + "\t port:" + ipconfig.getPort());
}
}
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("====爬取成功:一共爬取了" + ipConfigList.size() + "可用ip");
/*爬取1000条ip后停止,这段代码看个人吧,我这边不想爬取太多的ip*/
if (ipConfigList.size() > 1000) {
break;
}
}
});
index += 50;
index2 += 50;
}
/*关闭线程池*/
poolSend.close();
System.out.println("执行玩球了");
/*输出我们爬取成功可用的ip集合*/
for (Object o : ipConfigList) {
System.out.println(o);
}
/**
* 这个方法又是干球的?
* 咳咳,这个方法是用来将这个对象存入到某个文件中的,
* 参数就是存入要存的对象
*/
accessObject(ipConfigList);
return ipConfigList;
}
- 另外一个是爬取该网站的所有国内IP
/**
* 爬取该网站中所有的国内IP
*
* @throws Exception
*/
public static ArrayList<Ipconfig> crawl2() throws Exception {
ArrayList ipConfigList = new ArrayList<Ipconfig>();//要返回的集合
Random random = new Random();//用于生成随机线程休眠时间
/*创建一个线程池*/
PoolSend poolSend = new PoolSend(34, 34);
/*创建34个线程执行爬虫,每个线程爬一个页面*/
for (int i = 1; i <= 34; i++) {
int finalI = i;
//实现线程池
poolSend.send(() -> {
try {
//要爬取的url
URL url = new URL("http://www.66ip.cn/areaindex_" + finalI + "/1.html");
//当前线程休眠
Long s = (long) (random.nextInt(5000 - 1000 + 1) + 1000);
Thread.currentThread().sleep(s);
/*传入爬取的url和超时等待时间*/
Document document = Jsoup.parse(url, 1000000);
//通过css选择器来获取对应的标签
Elements table = document.select("table tr:not(tr:nth-child(1))");
//遍历获取IP地址
for (Element element : table) {
//ip
String ip = element.child(0).text();
//端口
String port = element.child(1).text();
//然后将ip和port封装成我们前面的那个对象
Ipconfig ipconfig = new Ipconfig(ip, port);
/**
* isValid?啊你tm这个方法是个啥?
* 这个方法是用来判断该ip是否可用的,可以看下面文章,有给出代码
* 返回true就表示这个ip得行,放心用
* 返回false的话。。。懂得都懂
*/
boolean valid = isValid(ipconfig);
if (valid) {
//可以用的ip就放入我们要返回的对象中
ipConfigList.add(ipconfig);
System.out.println("====爬取成功" + ipConfigList.size());
} else {
System.out.println("很遗憾,这个ip不合格:" + ipconfig.getIp() + "\t port:" + ipconfig.getPort());
}
}
System.out.println(url.toString() + "爬取完毕");
} catch (Exception e) {
e.printStackTrace();
}
});
}
//关闭线程池
poolSend.close();
/**
* 这个方法又是干球的?
* 咳咳,这个方法是用来将这个对象存入到某个文件中的,
* 参数就是存入要存的对象
*/
accessObject(ipConfigList);
return ipConfigList;
}
3、遍历测试IP是否可用
/**
* 检测代理ip是否有效
*
* @param ipBean
* @return
*/
public static boolean isValid(Ipconfig ipBean) {
/*设置代理
参数1:http
参数2:ip
参数3:端口
*/
Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(ipBean.getIp(), Integer.parseInt(ipBean.getPort())));
try {
/*访问百度*/
URLConnection httpCon = new URL("https://www.baidu.com/").openConnection(proxy);
httpCon.setConnectTimeout(10000);//连接超时
httpCon.setReadTimeout(10000);//读取超时如果不设置,它就会一直阻塞着。
//返回的一个响应code
int code = ((HttpURLConnection) httpCon).getResponseCode();
System.out.println(code);
//如果code==200就说明我们访问成功了!
return code == 200;
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
4、通过ObjectOutputStream将ip存集合作为对象存入到文件中
public static void accessObject(Object obj) throws Exception {
/*将这个对象存入到文件中*/
//1、创建ObjectOutputStream对象,构造方法中传递字节输出流
System.out.println("开始存入");
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("obj\\ipconfig.txt"));
//2、使用ObjectOutputStream对象中的方法writeObject,把对象写入文件中
oos.writeObject(obj);
//3、释放资源
oos.close();
}
5、通过ObjectInputStream读取ip集合对象文件
/**
* 读取文件对象
*
* @throws IOException
* @throws ClassNotFoundException
*/
public static ArrayList<Ipconfig> duqvObject() throws IOException, ClassNotFoundException {
//1、创建ObjectInputStream对象,构造方法中传递字节输入流
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("obj//ipconfig.txt"));
//2、使用ObjectInputStream对象中的方法readObject读取保存对象的文件
Object o = ois.readObject();
//3、释放资源
ois.close();
//4、使用读取出来对的对象(打印)
System.out.println(o);
ArrayList<Ipconfig> p = (ArrayList<Ipconfig>) o;//转换
return p;
}
有啥问题,欢迎讨论…