摘要:到了這個時候,我們已經(jīng)可以把京東的分類首頁的手機模塊給抓取下來,并且保存成。
GeccoSpider爬蟲例子
前些天,想要用爬蟲抓取點東西,但是網(wǎng)上很多爬蟲都是使用python語言的,本人只會java,因此,只能找相關(guān)java的爬蟲資料,在開源中國的看到國內(nèi)的大神寫的一個開源的爬蟲框架,并下源碼研究了一下,發(fā)現(xiàn)跟官網(wǎng)描述的一樣,夠簡單,簡潔易用!有興趣的朋友可以到官網(wǎng)了解下!
我這個例子也是在查看了官網(wǎng)的《教您使用java爬蟲gecco抓取JD全部商品信息》這篇博客之后,自己動手實現(xiàn)的,并且加入了持久化操作,由于京東的商品比較具有層次結(jié)構(gòu),類似一棵樹,因此,傳統(tǒng)的SQL數(shù)據(jù)庫很顯然不能很好存儲,于是我選用文檔型的NoSQL數(shù)據(jù)庫MongoDB在Monogo里存儲類似json的數(shù)據(jù),很容易表達出數(shù)據(jù)之間的層次關(guān)系。下面記錄一下我的實現(xiàn)過程,并且向Gecco作者大神致敬,也建議對這方面有興趣的朋友去官網(wǎng)查看作者的博客,會有更大的收獲,畢竟小弟水平有限,這里寫的也只是個人理解后實現(xiàn)的!
環(huán)境準(zhǔn)備jdk 我使用的是jdk1.8.0_74
IDE eclipse4.6
jar jar包有點多,主要是依賴包,所有的依賴包都在源碼中的lib目錄下。這里就不一一貼出來了
DB mongoDB 3.2.10
mongo driver 3.3
程序從cn.succy.geccospider.engine.jd.JDEngine這個類開始
public class JDEngine { public static void main(String[] args) { String url = "https://www.jd.com/allSort.aspx"; String classpath = "cn.succy.geccospider"; HttpRequest request = new HttpGetRequest(url); request.setCharset("gb2312"); // 如果pipeline和htmlbean不在同一個包,classpath就要設(shè)置到他們的共同父包 // 本引擎主要是把京東分類的頁面手機板塊給抓取過來封裝成htmlbean GeccoEngine.create().classpath(classpath).start(request).interval(2000).run(); // 本引擎是負(fù)責(zé)抓取每一個細(xì)目對應(yīng)的頁面的第一頁的所有商品列表的數(shù)據(jù),開啟5個線程同時抓取,提升效率 GeccoEngine.create().classpath(classpath).start(AllSortPipeline.cateRequests).thread(5) .interval(2000).run(); } }
在這段代碼里邊,總共用了兩個引擎,第一個先是抓取京東分類的入口url里邊的版塊入口 https://www.jd.com/allSort.aspx,在程序啟動的時候,底層會在指定的classpath包路徑下,尋找有> @Gecco注解的類,并且url和注解matchUrl對應(yīng),這個類就是抓取到的數(shù)據(jù)要映射成的HtmlBean,里邊的屬性可以通過注解把這個url下符合條件的節(jié)點對應(yīng)起來,從而,可以把我們想要的數(shù)據(jù)通過一個HtmlBean給封裝起來了。要注意一點,classpath應(yīng)該設(shè)置到能包含所有有類注解的類的包路經(jīng),如果同時兩個子包里邊的類都存在類注解,那么,這個classpath應(yīng)該就是設(shè)置到他們的共同父包下,以此類推……interval(2000)方法是指,隔多長時間執(zhí)行一次抓取,單位毫秒。下面我們看一下京東的分類頁面的結(jié)構(gòu),并且看一下我要抓取的是那一部分!
在這張圖片里邊,我們可以看到,實際上我們要抓取的是先把圈出部分抓取出來,把每一塊封裝成一個AllSort對象,下面我們看一下這個類!
@Gecco(matchUrl = "https://www.jd.com/allSort.aspx", pipelines = { "allSortPipeline", "consolePipeline" }) public class AllSort implements HtmlBean { private static final long serialVersionUID = 3422937382621558860L; @Request private HttpRequest request; /** * 抓取手機模塊的數(shù)據(jù) */ @HtmlField(cssPath = "div.category-items > div:nth-child(1) > div:nth-child(2) > div.mc > div.items > dl") private ListcellPhone; public HttpRequest getRequest() { return request; } public void setRequest(HttpRequest request) { this.request = request; } public List getCellPhone() { return cellPhone; } public void setCellPhone(List cellPhone) { this.cellPhone = cellPhone; } }
先看注解@Gecco ,這個注解里邊的matchUrl對應(yīng)的就是引擎開始爬的那個url,pipeline在我的理解是一個管道,玩過linux的朋友應(yīng)該知道linux的管道是什么,java里邊也有管道輸入輸出流,和這些相似,這里的大致意思是,當(dāng)這個類里邊的屬性都裝配好了之后,接著把這個類的對象當(dāng)成一個輸入條件,傳遞到pipline里邊配置好的pipleline類處理,pipeline類要實現(xiàn)一個叫做Pipeline的接口,并且通過@PipelineName注解指定這個pipeline叫啥名,關(guān)于Pipeline相關(guān)的,待會兒再說。實際上,在AllSort這個類里邊,把對應(yīng)選擇器選中的內(nèi)容,直接注入到一個叫做cellPhone的列表,關(guān)于這個待會兒再說。我們先看一下這個List
實際上,這里圈出的就是一個Category對象,手機模塊所有的Category就是List
public class Category implements HtmlBean { private static final long serialVersionUID = -1808704248579938878L; /** * 對應(yīng)的是大的分類名字,如手機通訊,運營商,手機配件等 */ @Text @HtmlField(cssPath = "dt > a") private String typeName; /** * 相對于上面的大的分類下的小類目名字 */ @HtmlField(cssPath = "dd > a") private Listcategories; public String getTypeName() { return typeName; } public void setTypeName(String typeName) { this.typeName = typeName; } public List getCategories() { return categories; } public void setCategories(List categories) { this.categories = categories; } }
也就是如下圖所示的標(biāo)簽對應(yīng)的文本和url
細(xì)心的朋友應(yīng)該會發(fā)現(xiàn),這里的@HtmlField(cssPath = "dd > a")的選擇器直接從dd開始,那是因為,在gecco里邊,Category對象是作為上面AllSort的一部分,因此,選擇器可以承接上級,也就是說,
@HtmlField(cssPath = "div.category-items > div:nth-child(1) > div:nth-child(2) > div.mc > div.items > dl") private ListcellPhone;
已經(jīng)到了dl這一層,那么它里邊的Category里的元素就可以直接從dl下面開始獲取,所以會看到像注解@HtmlField(cssPath = "dd > a")這種樣子的選擇器。
到了這個時候,我們已經(jīng)可以把京東的分類首頁的手機模塊給抓取下來,并且保存成javaBean。好了,下面可以說說對于注解上面的pipeline到底是什么,怎么用了!
上面我們也有說到,pipeline是一個管道連接,也就是當(dāng)頁面的HtmlBean解析完成后,再以此執(zhí)行注解中配置的所有的pipeline,我們在AllSort中配置有兩個pipeline,分別是"allSortPipeline","consolePipeline",第二個是往控制臺輸出,輸出的格式是json的格式的,這個沒有什么好講的,這里面我想說一下的是第一個,這個是我自定義的。好,接下來先上代碼看一下這個類是怎么實現(xiàn)的。
@PipelineName("allSortPipeline") public class AllSortPipeline implements Pipeline{ public static List cateRequests = new ArrayList<>(); @Override public void process(AllSort allSort) { List cellPhones = allSort.getCellPhone(); for (Category category : cellPhones) { // 獲取mongo的集合 MongoCollection collection = MongoUtils.getCollection(); // 把json轉(zhuǎn)成Document Document doc = Document.parse(JSON.toJSONString(category)); // 向集合里邊插入一條文檔 collection.insertOne(doc); List hrefs = category.getCategories(); // 遍歷HrefBean,取出里邊保存的url for (HrefBean href : hrefs) { HttpRequest request = allSort.getRequest(); // 把url保存起來,方便后面開啟一個新的引擎進行多線程抓取數(shù)據(jù) cateRequests.add(request.subRequest(href.getUrl())); } } } }
最上面的注解就是給這個pipeline起個名字,接著是把數(shù)據(jù)先入庫,入庫的數(shù)據(jù)長得像這樣子:
{“typeName":"手機通訊","categories":[{"title":"手機","url":"……"},……]}這種樣子,接下來就可以順著剛剛提取出來的每一個小類目的url進行抓取他們對應(yīng)的頁面的數(shù)據(jù)了,我們先看一下手機這個小類目對應(yīng)的頁面長什么樣的!如下圖
這里邊的商品item就是我們想要抓取的數(shù)據(jù),每一頁有60條,對應(yīng)的每個頁面封裝成一個ProductList類,具體規(guī)則抽取在上面已經(jīng)提及到,在這里就不再提及怎么提取規(guī)則了,相信看到這里的朋友,應(yīng)該可以依葫蘆畫瓢了,下面貼出代碼,看一下ProductList的實現(xiàn)類!
@Gecco(matchUrl = "https://list.jd.com/list.html?cat={cat}", pipelines = { "consolePipeline", "filePipeline" ,"mongoPipeline"}) public class ProductList implements HtmlBean { private static final long serialVersionUID = -6580138290566056728L; /** * 獲取請求對象,從該對象中可以獲取抓取的是哪個url */ @Request private HttpRequest request; // #plist > ul > li.gl-item > div.j-sku-item @HtmlField(cssPath = "#plist > ul > li.gl-item") private Listdetails; public HttpRequest getRequest() { return request; } public void setRequest(HttpRequest request) { this.request = request; } public List getDetails() { return details; } public void setDetails(List details) { this.details = details; } }
值得提一下的是,matchUrl里邊有一個{cat},這個是像url傳遞參數(shù),在HttpRequest里的url保存的就是填充參數(shù)之后url字符串,mongoPipeline就是對這里邊完成填充HtmlBean之后,執(zhí)行對應(yīng)mongoDB操作的pipeline,我們先看一下這個pipeline!
@PipelineName("mongoPipeline") public class MongoPipeline implements Pipeline{ @Override public void process(ProductList productList) { MongoCollection collection = MongoUtils.getCollection(); HttpRequest req = productList.getRequest(); // 從productList里邊獲取url,目的是為了從之前存進數(shù)據(jù)庫中找到對應(yīng)url的小類目 String url = req.getUrl(); // 把類目名對應(yīng)的商品詳情的列表獲取,例如,手機對應(yīng)到的頁面的60條記錄 List details = productList.getDetails(); // 轉(zhuǎn)成json字符串 String jsonString = JSON.toJSONString(details); // 通過url找到數(shù)組里邊對應(yīng)url的類目,然后添加一個字段叫做details,并且把details的值 // 給添加進去 collection.updateOne(new Document("categories.url", url), Document.parse("{"$set":{"categories.$.details":" + jsonString + "}}")); } }
上面代碼就可以實現(xiàn)通過獲取到請求對象里邊的url,因為url是唯一的,所以可以找到對應(yīng)的類目的信息,如下圖
這樣子就可以在這個記錄下面把抓取到的商品詳情列表插進去,表示該類目下面有這么多(60條)商品,保存進去之后,成下面的樣子。
我們看下ProductDetail怎么實現(xiàn)的,這也是一個普通的HtmlBean,和上面的沒有什么區(qū)別,都是規(guī)則抽取,然后把里邊想要的數(shù)據(jù)給注入到bean的屬性即可
public class ProductDetail implements HtmlBean { private static final long serialVersionUID = -6362237918542798717L; @Attr(value = "data-sku") @HtmlField(cssPath = "div.j-sku-item") private String pCode; @Image({ "data-lazy-img", "src" }) @HtmlField(cssPath = "div.j-sku-item > div.p-img > a > img") private String pImg; //#plist > ul > li:nth-child(1) > div > div.p-price > strong:nth-child(1) > i @Text @HtmlField(cssPath = "div.j-sku-item > div.p-price > strong:nth-child(1) > i") private String pPrice; @Text @HtmlField(cssPath = "div.j-sku-item > div.p-name > a > em") private String pTitle; @Text @HtmlField(cssPath = "div.j-sku-item > div.p-comment > strong > a.comment") private String pComment; @Text @HtmlField(cssPath = "div.j-sku-item > div.p-shop > span > a") private String pShop; @Text @HtmlField(cssPath = "div.j-sku-item > div.p-icons > *") private ListpIcons; public String getpCode() { return pCode; } public void setpCode(String pCode) { this.pCode = pCode; } public String getpImg() { return pImg; } public void setpImg(String pImg) { this.pImg = pImg; } public String getpPrice() { return pPrice; } public void setpPrice(String pPrice) { this.pPrice = pPrice; } public String getpTitle() { return pTitle; } public void setpTitle(String pTitle) { this.pTitle = pTitle; } public String getpComment() { return pComment; } public void setpComment(String pComment) { this.pComment = pComment; } public String getpShop() { return pShop; } public void setpShop(String pShop) { this.pShop = pShop; } public List getpIcons() { return pIcons; } public void setpIcons(List pIcons) { this.pIcons = pIcons; } }
到這里,基本上就已經(jīng)實現(xiàn)了數(shù)據(jù)的抓取和入庫了,從整體上來看,就只有一條數(shù)據(jù),呈一棵樹一樣,這種結(jié)構(gòu)如果用SQL數(shù)據(jù)庫做的話,會存在大量自連接,造成很多數(shù)據(jù)冗余,因此,使用文檔數(shù)據(jù)庫是最好不過的了
如果這個對您有所幫助,請點個Star喲
代碼已經(jīng)上傳到osc的代碼倉庫,由于本人網(wǎng)速不是非常好,因此并沒有使用maven管理項目,所有所需的jar包也都在項目源碼的lib包里邊,點擊源代碼下載
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/69803.html
摘要:到了這個時候,我們已經(jīng)可以把京東的分類首頁的手機模塊給抓取下來,并且保存成。 GeccoSpider爬蟲例子 前些天,想要用爬蟲抓取點東西,但是網(wǎng)上很多爬蟲都是使用python語言的,本人只會java,因此,只能找相關(guān)java的爬蟲資料,在開源中國的看到國內(nèi)的大神寫的一個開源的爬蟲框架,并下源碼研究了一下,發(fā)現(xiàn)跟官網(wǎng)描述的一樣,夠簡單,簡潔易用!有興趣的朋友可以到官網(wǎng)了解下! 我這個例...
摘要:是一個開源的簡單的爬蟲框架主要是通過將獲取的網(wǎng)頁信息封裝成來進行爬取信息。作者也是一個新手。這篇文章只是提供一個入門的思路。開啟多少個線程抓取隔多長時間抓取次部分。是用來抓取元素的連接是指獲取得到的內(nèi)容。并且這個類需要實現(xiàn)。 Gecco是一個開源的簡單的java爬蟲框架主要是通過將獲取的網(wǎng)頁信息封裝成HtmlBean來進行爬取信息。作者也是一個新手。這篇文章只是提供一個入門的思路。如果...
摘要:比如我們可以設(shè)置這就代表我們設(shè)置的規(guī)則對百度爬蟲是有效的。上一篇文章網(wǎng)絡(luò)爬蟲實戰(zhàn)使用解析鏈接下一篇文章網(wǎng)絡(luò)爬蟲實戰(zhàn)基本使用 上一篇文章:Python3網(wǎng)絡(luò)爬蟲實戰(zhàn)---22、使用Urllib:解析鏈接下一篇文章:Python3網(wǎng)絡(luò)爬蟲實戰(zhàn)---24、requests:基本使用 利用 Urllib 的 robotparser 模塊我們可以實現(xiàn)網(wǎng)站 Robots 協(xié)議的分析,本節(jié)我們來簡...
摘要:開源即時網(wǎng)絡(luò)爬蟲項目將與基于的異步網(wǎng)絡(luò)框架集成,所以本例將使用采集淘寶這種含有大量代碼的網(wǎng)頁數(shù)據(jù),但是要注意本例一個嚴(yán)重缺陷用加載網(wǎng)頁的過程發(fā)生在中,破壞了的架構(gòu)原則。 showImg(https://segmentfault.com/img/bVyzAX); 1,引言 本文講解怎樣用Python驅(qū)動Firefox瀏覽器寫一個簡易的網(wǎng)頁數(shù)據(jù)采集器。開源Python即時網(wǎng)絡(luò)爬蟲項目將與S...
摘要:一般用進程池維護,的設(shè)為數(shù)量。多線程爬蟲多線程版本可以在單進程下進行異步采集,但線程間的切換開銷也會隨著線程數(shù)的增大而增大。異步協(xié)程爬蟲引入了異步協(xié)程語法。 Welcome to the D-age 對于網(wǎng)絡(luò)上的公開數(shù)據(jù),理論上只要由服務(wù)端發(fā)送到前端都可以由爬蟲獲取到。但是Data-age時代的到來,數(shù)據(jù)是新的黃金,毫不夸張的說,數(shù)據(jù)是未來的一切。基于統(tǒng)計學(xué)數(shù)學(xué)模型的各種人工智能的出現(xiàn)...
閱讀 2064·2023-04-25 22:58
閱讀 1408·2021-09-22 15:20
閱讀 2694·2019-08-30 15:56
閱讀 1986·2019-08-30 15:54
閱讀 2101·2019-08-29 12:31
閱讀 2728·2019-08-26 13:37
閱讀 592·2019-08-26 13:25
閱讀 2098·2019-08-26 11:58