一,什麼是網絡爬蟲?
網絡爬蟲(web crawer),是一種按照必定的規則,自動的抓取萬維網信息的程序或者腳本。從功能上來說,爬蟲通常分爲數據採集,處理,儲存三個部分。css
1,入門程序
環境準備html
(1)jdk1.8 (2)idea環境 (3)mavenjava
(4)須要導入httpClient的依賴。(去官網找用的最多的一個版本,不要找最新的)node
<!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient --> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.2</version> </dependency>
2,寫一個爬蟲小例子帶你初次體驗爬蟲
這裏寫一個測試類,把傳智播客官網首頁的代碼全都爬出來。mysql
public class CrawerFirst { public static void main(String[] args) throws IOException { //1,打開瀏覽器,建立HTTPClient對象 CloseableHttpClient httpClient = HttpClients.createDefault(); //2,輸入網址,發起get請求,建立httpGet對象 HttpGet httpGet=new HttpGet("http://www.itcast.cn"); //3,按回車發送請求,返回響應,使用HttpClient對象發起請求 CloseableHttpResponse response = httpClient.execute(httpGet); //4,解析響應,獲取數據 //判斷狀態碼是否爲200 if(response.getStatusLine().getStatusCode()==200){ HttpEntity httpEntity=response.getEntity(); String content = EntityUtils.toString(httpEntity, "utf-8"); System.out.println(content); } } }
而後就能夠打印出content,即首頁的全部html代碼信息。jquery
3,HttpClient
這裏咱們使用Java的Http協議客戶端HttpClient這個技術,來實現抓取網頁數據。web
3.1 Get請求
public static void main(String[] args) throws IOException { //建立HttpClient對象 CloseableHttpClient httpClient = HttpClients.createDefault(); //建立HttpGet對象,設置url訪問地址 HttpGet httpGet=new HttpGet("http://www.itcast.cn"); //使用httpClient發起請求,獲取response CloseableHttpResponse response = null; try{ response=httpClient.execute(httpGet); //解析響應 if(response.getStatusLine().getStatusCode()==200){ //獲得響應體,並把結果經過EntityUtils工具類把結果轉換爲字符串 String content= EntityUtils.toString(response.getEntity(),"utf8"); System.out.println(content.length()); } }catch (Exception e){ e.printStackTrace(); }finally { //關閉response response.close(); httpClient.close(); } }
3.2 帶參數的Get請求
經過URIBuilder來設置參數。正則表達式
public class HttpGetTest { public static void main(String[] args) throws Exception { //建立HttpClient對象 CloseableHttpClient httpClient = HttpClients.createDefault(); //設置請求地址是:http://yun.itheima.com/search?keys=Java //建立URIBuilder URIBuilder uriBuilder=new URIBuilder("http://yun.itheima.com/search"); //設置參數 uriBuilder.setParameter("keys","Java"); //建立HttpGet對象,設置url訪問地址 HttpGet httpGet=new HttpGet(uriBuilder.build()); System.err.println("發送的請求是"+httpGet); //使用httpClient發起請求,獲取response CloseableHttpResponse response = null; try{ response=httpClient.execute(httpGet); //解析響應 if(response.getStatusLine().getStatusCode()==200){ //獲得響應體,並把結果經過EntityUtils工具類把結果轉換爲字符串 String content= EntityUtils.toString(response.getEntity(),"utf8"); System.out.println(content.length()); } }catch (Exception e){ e.printStackTrace(); }finally { //關閉response response.close(); httpClient.close(); } } }
3.3 不帶參數的 Post請求
不帶參數的post請求和get請求的區別只有一個,就是請求的聲明。spring
//get請求 HttpGet httpGet=new HttpGet("url路徑地址"); //post請求 HttpPost httpPost=new HttpPost("url路徑地址");
3.4 帶參數的Post請求
帶參的話,使用post請求,url地址沒有參數,參數keys=Java放在表單中進行提交。sql
public static void main(String[] args) throws Exception { //建立HttpClient對象 CloseableHttpClient httpClient = HttpClients.createDefault(); //設置請求地址是:http://yun.itheima.com/search?keys=Java //建立HttpPost對象,設置url訪問地址 HttpPost httpPost=new HttpPost("http://yun.itheima.com/search"); //聲明list集合,封裝表單中的參數 List<NameValuePair> params=new ArrayList<NameValuePair>(); //設置參數 params.add(new BasicNameValuePair("keys","Java")); //建立表單的Entity對象,第一個參數就是封裝好的表單數據,第二個參數就是編碼 UrlEncodedFormEntity formEntity=new UrlEncodedFormEntity(params,"utf8"); //設置表單的Entity對象到post請求中 httpPost.setEntity(formEntity); //使用httpClient發起請求,獲取response CloseableHttpResponse response = null; try{ response=httpClient.execute(httpPost); //解析響應 if(response.getStatusLine().getStatusCode()==200){ //獲得響應體,並把結果經過EntityUtils工具類把結果轉換爲字符串 String content= EntityUtils.toString(response.getEntity(),"utf8"); System.out.println(content.length()); } }catch (Exception e){ e.printStackTrace(); }finally { //關閉response response.close(); httpClient.close(); } }
3.5 鏈接池
若是每次請求都要建立HttpClient,會有頻繁建立和銷燬的問題,可使用鏈接池來解決這個問題。
public class HttpClientPoolTest { public static void main(String[] args) throws Exception { //建立鏈接池管理器 PoolingHttpClientConnectionManager cm=new PoolingHttpClientConnectionManager(); //設置最大鏈接數 cm.setMaxTotal(100); //設置每一個主機的最大鏈接數 cm.setDefaultMaxPerRoute(10); //使用鏈接池管理器發送請求 doGet(cm); } private static void doGet(PoolingHttpClientConnectionManager cm) throws Exception { //不是每次都建立新的HttpClient,而是從鏈接池中獲取HttpClient對象 CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(cm).build(); HttpGet httpGet=new HttpGet("http://www.itcast.cn"); CloseableHttpResponse response=null; try{ response=httpClient.execute(httpGet); if(response.getStatusLine().getStatusCode()==200){ String content = EntityUtils.toString(response.getEntity(), "utf8"); System.out.println(content.length()); } }catch (Exception e){ throw new Exception("發生異常"); }finally { if(response!=null){ response.close(); } //不能關閉HttpClient,由鏈接池管理HttpClient //httpClient.close(); } } }
4,請求參數(配置請求信息RequestConfig)
有時候由於網絡,或者目標服務器的緣由,請求須要更長的時間才能完成,我麼須要自定義相關時間。
public class HttpConfigTest { public static void main(String[] args) { //建立HttpClient對象 CloseableHttpClient httpClient = HttpClients.createDefault(); //建立httpGet對象,設置url訪問地址 HttpGet httpGet=new HttpGet("http://www.itcast.cn"); //配置請求信息 RequestConfig config=RequestConfig.custom().setConnectTimeout(1000) //建立鏈接的最長時間,單位是毫秒 .setConnectionRequestTimeout(500) //設置獲取鏈接的最長時間,單位是毫秒 .setSocketTimeout(10*1000) //設置數據傳輸的最長時間,單位是毫秒 .build(); //給請求設置請求信息 httpGet.setConfig(config); } }
二,Jsoup
咱們抓取到頁面以後,還須要對頁面進行解析,可使用字符串處理工具解析頁面,也可使用正則表達式,可是這些方法都會帶來很大的開發成本,因此咱們須要使用一款專門解析html頁面的技術。
2.1 Jsoup介紹
jsoup是一款java的html解析器,可直接解析某個url地址,html文本等內容,它提供了一套很是省力的api,可經過dom,css以及相似於jquery的操做方法來取出和操做數據。
Jsonp的主要功能以下:
1,從一個url,文件或字符串中解析html;
2,使用dom或css選擇器來查找、取出數據。
2.2 使用Jsoup須要導入的依賴
<!--Jsonp--> <!-- https://mvnrepository.com/artifact/org.jsoup/jsoup --> <dependency> <groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId> <version>1.10.2</version> </dependency> <!-- https://mvnrepository.com/artifact/junit/junit junit 測試工具類,只是用於測試--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <!-- https://mvnrepository.com/artifact/commons-io/commons-io 這裏是使用到了這個jar包的一個util工具類,lang3也是 --> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.4</version> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.9</version> </dependency>
2.3 Jsoup解析URL
這裏寫一個小例子,抓取黑馬官網主頁的title的內容。
@Test public void testUrl() throws Exception{ //解析url地址,第一個參數是訪問的url地址,第二個參數是訪問時候的超時時間。 //返回類型是一個dom對象,能夠理解爲抓取到的html頁面。 Document doc = Jsoup.parse(new URL("http://www.itcast.cn"), 1000); //使用標籤選擇器,獲取title標籤中的內容 String title = doc.getElementsByTag("title").first().text();//第一個的文本內容 System.out.println(title); }
[外鏈圖片轉存失敗(img-szceJTJD-1567139877894)(D:\文件筆記\image\1566883592525.png)]
<font color="red">說明:</font> 雖然使用Jsoup能夠替代HttpClient直接發起請求解析數據,可是每每不會這樣用,由於實際的開發過程當中,須要使用到多線程,鏈接池,代理等等方式,而Jsoup對這些的支持並非很好,因此咱們通常把jsoup僅僅做爲html解析工具使用。
2.4 Jsoup解析字符串
@Test public void testString() throws Exception{ //使用工具類讀取文件,獲取字符串 String content=FileUtils.readFileToString(new File("D:\\IdeaProjects\\黨建項目 \\client\\src\\main\\resources\\templates\\web\\demo\\student\\lzjj_test.html"),"utf8"); //解析字符串 Document doc = Jsoup.parse(content); //獲取title內容 String title = doc.getElementsByTag("title").first().text(); System.out.println(title); }
[外鏈圖片轉存失敗(img-6UmUVM3Q-1567139877896)(D:\文件筆記\image\1566885700972.png)]
2.5 Jsoup解析文件
@Test public void testFile() throws Exception{ //解析文件 Document doc = Jsoup.parse(new File("D:\\IdeaProjects\\黨建項目\\light-client\\src\\main\\resources\\templates\\web\\demo\\student\\lzjj_test.html"), "utf8"); String title=doc.getElementsByTag("title").first().text(); System.out.println(title); }
[外鏈圖片轉存失敗(img-fhj4dlUk-1567139877896)(D:\文件筆記\image\1566885760260.png)]
2.6 使用dom的方式獲取元素
@Test public void testDom() throws Exception{ //解析文件,獲取document對象 Document doc = Jsoup.parse(new File("D:\\IdeaProjects\\黨建項目\\light-client\\src\\main\\resources\\templates\\web\\demo\\student\\lzjj_test.html"), "utf8"); //獲取元素 //根據id獲取 /*Element a = doc.getElementById("a"); System.out.println(a.text());*/ //根據標籤獲取 Element element = doc.getElementsByTag("td").last(); System.out.println(element); //根據class類獲取 Element test = doc.getElementsByClass("test").first(); //根據屬性獲取 Elements abc = doc.getElementsByAttribute("abc"); //經過指定的屬性名和屬性值指定獲取 Elements href = doc.getElementsByAttributeValue("href", "www.baidu.com"); }
2.7 獲取元素中的數據
上一步已經獲取到了元素,怎麼獲取到元素中的諸多數據呢?
1,從元素中獲取id
2,從元素中獲取className
3,從元素中獲取屬性的值attr
4,從元素中獲取全部屬性attributes
5,從元素中獲取文本內容text
@Test public void testData() throws Exception{ //解析文件,獲取document對象 Document doc = Jsoup.parse(new File("D:\\IdeaProjects\\黨建項目\\light-client\\src\\main\\resources\\templates\\web\\demo\\student\\lzjj_test.html"), "utf8"); Element element = doc.getElementsByTag("td").last(); //獲取元素的id值 String id = element.id(); //獲取元素的class類的值(className) String className = element.className(); System.out.println(className); //若是className的值是有多個class組成,這裏獲取每個className,把它們拆分開 Set<String> strings = element.classNames(); for(String s:strings){ System.out.println(s); } //從元素中獲取class屬性的值attr String aClass = element.attr("class"); //從元素中獲取文本內容text String text = element.text(); }
2.8 使用組合選擇器獲取元素
@Test public void testSelectors() throws Exception{ //解析文件,獲取document對象 Document doc = Jsoup.parse(new File("D:\\IdeaProjects\\黨建項目\\light-client\\src\\main\\resources\\templates\\web\\demo\\student\\lzjj_test.html"), "utf8"); //元素 + ID Element element = doc.select("p#lese").first(); //元素 + class Element ele = doc.select("p.lese").first(); //元素 + 屬性名 Elements select = doc.select("p[abc]"); //任意組合(元素+class+id+屬性名的任意組合) Element first = doc.select("p[abc].lese").first(); //查找某個元素下的子元素 好比 .city li Element first1 = doc.select(".city li").first(); //查找某個元素下的直接子元素 好比 .city>li Element first2 = doc.select(".city>ul>li").first(); //parent > * 查找某個父元素下的全部直接子元素 Element first3 = doc.select(".city>ul>*").first(); System.out.println(first); }
三,案例--抓取京東的商品信息
這裏只抓取京東的一部分數據就好了,商品的圖片,價格,顏色等信息。
3.1 先在數據庫建表
[外鏈圖片轉存失敗(img-d6avaMTn-1567139877896)(D:\文件筆記\image\1566984565304.png)]
3.2 添加依賴
使用springboot+spring Data JPA和定時任務完成開發。
須要建立maven工程並添加如下依賴。
<dependencies> <!-- springMVC --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>2.1.3.RELEASE</version> </dependency> <!-- Mysql --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.13</version> </dependency> <!-- httpClient用於抓取數據 --> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.2</version> </dependency> <!--Jsonp--> <!-- 用於解析數據 --> <dependency> <groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId> <version>1.10.2</version> </dependency> <!--junit 測試工具類--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <!-- https://mvnrepository.com/artifact/commons-io/commons-io --> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.4</version> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.9</version> </dependency> <!--springboot data-jpa --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> <version>2.1.4.RELEASE</version> </dependency> </dependencies>
3.3 添加配置文件
加入application.properties配置文件
# DB 配置 spring.datasource.driver-class-name=com.mysql.jdbc.Driver spring.datasource.url=jdbc:mysql://127.0.0.1:3306/jsoup spring.datasource.username=root spring.datasource.password=1234 # JPA 配置 spring.jpa.database=mysql spring.jpa.show-sql=true
3.4 代碼實現
先寫pojo類
@Entity @Table(name = "jd_item") public class item { private Long id; private Long spu; private Long sku; private String title; private double price; private String pic; private String url; private Date created; private Date updated; }
3.5 封裝HttpClient
咱們常常要使用HttpClient,因此須要進行封裝,方便使用。
package com.qianlong.jd.util; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.util.EntityUtils; import org.springframework.stereotype.Component; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.UUID; @Component //建立實例 public class HttpUtils { //使用鏈接池 private PoolingHttpClientConnectionManager cm; //須要聲明構造方法,由於參數不須要從外面傳進來,因此不須要參數 //爲何須要構造方法,是由於聲明的這個鏈接池須要賦於屬性的值 public HttpUtils() { this.cm = new PoolingHttpClientConnectionManager(); //設置最大鏈接數 this.cm.setMaxTotal(100); //設置每一個主機的最大鏈接數 this.cm.setDefaultMaxPerRoute(10); } //這裏使用get請求獲取頁面數據,返回類型是string字符串類型 /** * 根據請求地址下載頁面數據 * @param url * @return */ public String doGetHTML(String url){ //獲取HttpClient對象 CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(cm).build(); //建立httpGet對象,設置url地址 HttpGet httpGet=new HttpGet(url); //設置請求信息 httpGet.setConfig(this.getConfig()); CloseableHttpResponse response=null; try { //使用httpClient發起請求,獲取響應 response=httpClient.execute(httpGet); //解析響應,返回結果 if(response.getStatusLine().getStatusCode()==200){ //判斷響應體Entity是否爲空,若是不爲空就可使用HttpUtils if(response.getEntity()!=null){ String content = EntityUtils.toString(response.getEntity(), "utf8"); } } } catch (IOException e) { e.printStackTrace(); }finally { //關閉response if(response!=null){ try { response.close(); } catch (IOException e) { e.printStackTrace(); } } } return ""; } //設置請求的信息 private RequestConfig getConfig() { RequestConfig config=RequestConfig.custom() .setConnectTimeout(1000)//建立鏈接的最長時間 .setConnectionRequestTimeout(500)//獲取鏈接的最長時間 .setSocketTimeout(500)//數據傳輸的最長時間 .build(); return config; } /** * 下載圖片 * @param url * @return */ public String doGetImage(String url){ //獲取HttpClient對象 CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(cm).build(); //建立httpGet對象,設置url地址 HttpGet httpGet=new HttpGet(url); //設置請求信息 httpGet.setConfig(this.getConfig()); CloseableHttpResponse response=null; try { //使用httpClient發起請求,獲取響應 response=httpClient.execute(httpGet); //解析響應,返回結果 if(response.getStatusLine().getStatusCode()==200){ //判斷響應體Entity是否爲空,若是不爲空就可使用HttpUtils if(response.getEntity()!=null){ //下載圖片 //獲取圖片的後綴 String extName=url.substring(url.lastIndexOf(".")); //建立圖片名,重命名圖片 String picName= UUID.randomUUID().toString()+extName; //下載圖片 //聲明OutputStream OutputStream outputStream=new FileOutputStream(new File("D:\\suibian\\image")+picName); response.getEntity().writeTo(outputStream); //圖片下載完成,返回圖片名稱 return picName; } } } catch (IOException e) { e.printStackTrace(); }finally { //關閉response if(response!=null){ try { response.close(); } catch (IOException e) { e.printStackTrace(); } } } return ""; } }
3.6 實現數據抓取
使用定時任務,能夠定時抓取最新的數據。
先寫好springboot的啓動類(這裏就不仔細說明啓動類文件的位置了,和包同級)
//使用定時任務,須要先開啓定時任務,須要添加註解 @EnableScheduling @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class,args); } }
而後就開始寫主角了,開始抓取數據
package com.qianlong.jd.task; import com.qianlong.jd.pojo.Item; import com.qianlong.jd.service.ItemService; import com.qianlong.jd.service.ItemServiceImpl; import com.qianlong.jd.util.HttpUtils; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.select.Elements; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import java.util.List; @Component public class ItemTask { @Autowired private HttpUtils httpUtils; @Autowired private ItemService itemService; //當下載任務完成後,間隔100秒進行下一次的任務 @Scheduled(fixedDelay = 100*1000) public void itemTask() throws Exception{ //聲明須要解析的初始地址 String url="https://search.jd.com/Search?keyword=iphone&enc=utf-8&qrst=1&rt=1&stop=1&vt=2&wq=iphone&page=1&s=1&click="; //這裏下載前五頁(1,3,5,7---順序) //按照頁面對手機的搜索結果進行遍歷解析 for(int i=1;i<10;i=i+2){ String html=httpUtils.doGetHTML(url+i); //解析頁面,獲取商品數據並存儲 parse(html); } System.out.println("手機數據抓取完成"); } /** * 對頁面進行解析 * @param html */ private void parse(String html) throws Exception { System.err.println("進到了解析方法"); //解析html獲取dom對象 Document dom = Jsoup.parse(html); //獲取spu信息 Elements elements = dom.select("div#J_goodsList>ul>li"); for(Element element:elements){ //獲取spu long spu=Long.parseLong(element.attr("data-spu")); //獲取sku信息 Elements elements1 = element.select("li.ps-item"); for(Element skuEle:elements1){ //獲取sku long sku=Long.parseLong(skuEle.select("[data-sku]").attr("data-sku")); System.err.println(sku); //根據sku查詢商品信息 Item item=new Item(); item.setSku(sku); List<Item> list = itemService.findAll(item); //若是商品存在,就進行下一個循環,該商品不保存,由於已存在 if(list.size()>0){ continue; } //設置商品的spu item.setSpu(spu); //獲取商品的詳情的url String itemUrl="https://item.jd.com/"+sku+".html"; item.setUrl(itemUrl); //獲取商品的圖片 String picUrl = "https:"+skuEle.select("img[data-sku]").first().attr("data-lazy-img"); String picName=httpUtils.doGetImage(picUrl); item.setPic(picName); //保存數據到數據庫中 itemService.save(item); } } } }
來到這裏案例基本已經結束了,接下來就是處理dao的數據了,插入數據到數據庫,這裏省略。
<font color="blue">到這裏爬蟲已經結束了,上面是Java爬蟲的基礎,能夠實現一些小的demo,好比爬取一個網站的部分數據,可是在實際的爬蟲項目中使用的都是爬蟲框架,例如WebMagic
框架,底層使用的就是HttpClient和Jsoup,更方便的開發爬蟲,同時內置了一些經常使用的組件,便於爬蟲開發。若是你想更深的學習爬蟲的話,你必須深刻學習那些更優秀的框架才行,以上是實現爬蟲的基礎內容。 </font> 若是想看源碼的話就本身下載,若是以爲還不錯的話就留下你的足跡吧!
項目連接:https://pan.baidu.com/s/1ArXk_QlmtbhzW_wfMrerFw 提取碼:sqms