JAVA 多線程爬蟲實(shí)例詳解
JAVA 多線程爬蟲實(shí)例詳解
前言
以前喜歡Python的爬蟲是出于他的簡潔,但到了后期需要更快,更大規(guī)模的爬蟲的時(shí)候,我才漸漸意識到Java的強(qiáng)大。Java有一個(gè)很好的機(jī)制,就是多線程。而且Java的代碼效率執(zhí)行起來要比python快很多。這份博客主要用于記錄我對多線程爬蟲的實(shí)踐理解。
線程
線程是指一個(gè)任務(wù)從頭至尾的執(zhí)行流。線程提供了運(yùn)行一個(gè)任務(wù)的機(jī)制。對于Java而言,可以在一個(gè)程序中并發(fā)地啟動多個(gè)線程。這些線程可以在多處理器系統(tǒng)上同時(shí)運(yùn)行。
runnable接口
任務(wù)類必須實(shí)現(xiàn)runnable接口,它只包含一個(gè)run方法。需要實(shí)現(xiàn)這個(gè)方法來告訴系統(tǒng)線程將如何運(yùn)行。
Thread類
包含為任務(wù)而創(chuàng)建的線程的構(gòu)造方法,以及控制線程的方法。
synchronized關(guān)鍵字
為避免競爭狀態(tài),防止多個(gè)線程同時(shí)進(jìn)入程序的某個(gè)特定部分,即臨界區(qū),以便一次只有一個(gè)線程可以訪問臨界區(qū)。
利用加鎖同步
Java可以顯式加鎖,一個(gè)鎖是一個(gè)Lock接口的實(shí)例,它定義了加鎖和釋放鎖的方法。
線程池
線程池是管理開發(fā)執(zhí)行任務(wù)個(gè)數(shù)的理想方法。Java提供Executor接口來執(zhí)行線程池中的任務(wù),提供ExecutorService接口管理和控制任務(wù)。
使用線程池的方法獲取url列表
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/*
* 獲取京東評論url列表
*/
public class MyThreading {
private static String p_id = null;
private static Url urls = null;
public MyThreading(String p_id){
this.p_id = p_id ; // 京東商品的id
urls = new Url(p_id);
}
public List<String> getUriList(){
ExecutorService executor = Executors.newCachedThreadPool();
for (int i = 0 ; i < 600 ; i ++){
executor.execute(new AddUrl(i)); // 添加任務(wù)到線程池
}
executor.shutdown();
while (!executor.isTerminated()){}
return urls.getList();
}
public static class AddUrl implements Runnable{
int page;
public AddUrl(int page){
this.page = page;
}
public void run(){
urls.addList(page); // 啟動多線程任務(wù)
}
}
public static class Url {
private static Lock lock = new ReentrantLock(); // 開啟顯式家鎖
private static List<String> urlList = new ArrayList();
private String p_id;
public Url(String p_id ){
this.p_id = p_id ;
}
public List<String> getList(){
return urlList;
}
public void addList(int page){
lock.lock();
try{
String url = "http://club.jd.com/productpage/p-" + p_id + "-s-0-t-0-p-" + String.valueOf(page) + ".html";
// Thread.sleep(5);
urlList.add(url); //添加url到url列表
}catch(Exception ex ){
}
finally {
lock.unlock(); // 解鎖
}
}
}
public static void main(String[] args) {
String p_id = "2441288";
MyThreading myThreading = new MyThreading(p_id);
List <String> urlList = myThreading.getUriList();
for(String url : urlList){
System.out.println(url);
}
System.out.println(urlList.size());
}
}
代碼分析
- 代碼的作用:獲取京東評論的url列表
- 類的說明:MyThreading是主類, AddUrl和Url是它的內(nèi)部類,AddUrl實(shí)現(xiàn)了runnable的接口,主要啟動多線程服務(wù)運(yùn)行Url的addList方法。而Url是最內(nèi)核的部分 ,他提供addList任務(wù)和多線程的共享區(qū)域urlList,所以在實(shí)現(xiàn)添加url的步驟中,需要對urlList加鎖。
- 線程池主要有兩種類型,一個(gè)是固定線程池,即newFixedThreadPool;另一個(gè)是newCachedThreadPool,這個(gè)主要利用了緩沖機(jī)制,能動態(tài)地添加線程。在上述代碼中,我主要使用了newCachedthreadPool.
使用線程池的方法根據(jù)url列表爬取網(wǎng)頁元素
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class ThreadingCrawel {
private static Content content = null;
private static List<String> urlList = null;
public ThreadingCrawel(List<String> urlList){
this.urlList = urlList;
content = new Content();
}
public List<String> getContent(){
ExecutorService executor = Executors.newCachedThreadPool();
for (String url : urlList){
executor.execute(new AddContent(url));
}
executor.shutdown();
while(!executor.isTerminated()){}
return content.getContent();
}
public static class AddContent implements Runnable{
String url;
public AddContent(String url){
this.url = url;
}
public void run(){
content.addContent(url);
}
}
public static class Content {
private static Lock lock = new ReentrantLock();
private static List<String> contentList = new ArrayList();
public void addContent(String url){
String content = "";
BufferedReader in = null;
try{
URL realUrl = new URL(url);
URLConnection connection = realUrl.openConnection();
in = new BufferedReader(new InputStreamReader(connection.getInputStream(), "gbk"));
String line;
while( (line = in.readLine()) != null){
content += line +"\n";
}
}catch(Exception e){
e.printStackTrace();
}
finally{
try{
if (in != null){
in.close();
}
}catch(Exception e2){
e2.printStackTrace();
}
}
Pattern p = Pattern.compile("content\":\".*?\"");
Matcher match = p.matcher(content);
String tmp;
lock.lock();
while(match.find()){
tmp = match.group();
tmp = tmp.replaceAll("\"", "");
tmp = tmp.replace("content:", "");
tmp = tmp.replaceAll("<.*?>", "");
contentList.add(tmp);
try {
Thread.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
lock.unlock();
}
public List getContent(){
return contentList;
}
}
public static void main(String[] args){
long start = System.currentTimeMillis();
String p_id = "2441288";
MyThreading myThreading = new MyThreading(p_id);
List <String> urlList = myThreading.getUriList();
ThreadingCrawel threadingCrawel = new ThreadingCrawel(urlList);
List <String> contentList = threadingCrawel.getContent();
for(String content : contentList){
System.out.println(content);
}
long end = System.currentTimeMillis();
System.out.println(end - start);
}
}
感謝閱讀,希望能幫助到大家,謝謝大家對本站的支持!
相關(guān)文章
SpringCloud Netflix Ribbon超詳細(xì)講解
這篇文章主要介紹了SpringCloud筆記HoxtonNetflix之Ribbon負(fù)載均衡,Ribbon是管理HTTP和TCP服務(wù)客戶端的負(fù)載均衡器,Ribbon具有一系列帶有名稱的客戶端(Named Client),對SpringCloud Ribbon負(fù)載均衡相關(guān)知識感興趣的朋友一起看看吧2022-10-10
SpringBoot項(xiàng)目部署到服務(wù)器的兩種方式
目前,前后端分離的架構(gòu)已成主流,而使用SpringBoot構(gòu)建Web應(yīng)用是非??焖俚?項(xiàng)目發(fā)布到服務(wù)器上的時(shí)候,只需要打成一個(gè)jar包,然后通過命令 : java -jar jar包名稱即可啟動服務(wù)了,本文介紹了SpringBoot項(xiàng)目部署到服務(wù)器的兩種方式,需要的朋友可以參考下2024-10-10
spring boot 配置動態(tài)刷新實(shí)現(xiàn)詳解
這篇文章主要介紹了spring boot 配置動態(tài)刷新實(shí)現(xiàn)詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-09-09
java WSDL接口webService實(shí)現(xiàn)方式
這篇文章主要為大家詳細(xì)介紹了java WSDL接口webService實(shí)現(xiàn)方式的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-04-04
Java利用httpclient通過get、post方式調(diào)用https接口的方法
這篇文章主要介紹了Java利用httpclient通過get、post方式調(diào)用https接口的方法,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-02-02
Java中關(guān)鍵字final finally finalize的區(qū)別介紹
這篇文章主要給大家分享的是 Java中final,finally,finalize 到底有什么區(qū)別,文章圍繞final,finally,finalize的相關(guān)資料展開詳細(xì)內(nèi)容,具有一定的參考的價(jià)值,需要的朋友可以參考一下2022-04-04

