背景:
公司要做一個電商的網站,而該項目是由J2EE架構完成,項目經理說要讓Java代碼自助每天生成電子商務網站的Sitemap檔案,然後開始上網各種查資料!!!
然而,終于碰上了本人有生以來第一個在網上沒找到的具體答案的東西,自己幹吧,不過網上也有做過類似的,隻不過人家應該是大牛吧,沒有說的很詳細,隻好自己慢慢領悟了.
按照大牛給的方向,擺在我面前一下出現三個問題:1,擷取網站所有連結。 2,生成XML檔案 3,定時調用,後兩種問題如果做過Java程式的人,應該比較好解決。(這句話是大牛說的),這篇文章解決的是第一個問題和第二個問題,如何獲得網站的所有連結并生成sitemap.xml檔案
###代碼:
package com.langgufoeng.test.entity;
/**
* 标題: urlEntity.java
* 路徑: com.langgufoeng.test.entity
* 描述: TODO sitemap.xml中每個連結對應的實體
* 作者: 郎國峰
* 時間: 2018-1-7 下午2:50:08
* 版本: @version V1.0
*/
public class UrlEntity {
private String loc;
private String lastmod;
private String changefreq;
private String priority;
public String getLoc() {
return loc;
}
public void setLoc(String loc) {
this.loc = loc;
}
public String getLastmod() {
return lastmod;
}
public void setLastmod(String lastmod) {
this.lastmod = lastmod;
}
public String getChangefreq() {
return changefreq;
}
public void setChangefreq(String changefreq) {
this.changefreq = changefreq;
}
public String getPriority() {
return priority;
}
public void setPriority(String priority) {
this.priority = priority;
}
@Override
public String toString() {
return "UrlEntity [loc=" + loc + ", lastmod=" + lastmod
+ ", changefreq=" + changefreq + ", priority=" + priority + "]";
}
}
package com.langgufoeng.test.entity;
import java.util.List;
/**
* 标題: UrlsetEntity.java
* 路徑: com.langgufoeng.test.entity
* 描述: TODO sitemap.xml對應的實體
* 作者: 郎國峰
* 時間: 2018-1-7 下午2:51:15 版本: @version V1.0
*/
public class UrlsetEntity {
static List<UrlEntity> list;
public static List<UrlEntity> getList() {
return list;
}
public static void setList(List<UrlEntity> list) {
UrlsetEntity.list = list;
}
}
package com.langgufoeng.test;
import java.io.File;
import java.net.MalformedURLException;
import java.util.Date;
import java.util.List;
import com.langgufoeng.test.entity.UrlEntity;
import com.langgufoeng.test.entity.UrlsetEntity;
import com.redfin.sitemapgenerator.ChangeFreq;
import com.redfin.sitemapgenerator.WebSitemapGenerator;
import com.redfin.sitemapgenerator.WebSitemapUrl;
/**
* 标題: EntityToSitemap.java
* 路徑: com.langgufoeng.test
* 描述: TODO 根據實體類生成sitemap
* 作者: 郎國峰
* 時間: 2018-1-7 下午3:00:01
* 版本: @version V1.0
*/
public class EntityToSitemap {
static public void toSitemap(){
WebSitemapGenerator sitemapGenerator = null;
try {
// 壓縮輸出 true 不壓縮輸出false
sitemapGenerator = WebSitemapGenerator.builder("http://www.baidu.com", new File("WebRoot")).gzip(false).build();
//得到sitemap實體
List<UrlEntity> sitemapList = UrlsetEntity.getList();
//周遊實體,得到sitemapUrl資訊
for (int i = 0; i < sitemapList.size(); i++) {
UrlEntity sitemap = sitemapList.get(i);
System.out.println("url路徑"+sitemap.getLoc());
WebSitemapUrl sitemapUrl = new WebSitemapUrl.Options(sitemap.getLoc()).build();
sitemapGenerator.addUrl(sitemapUrl);
}
} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
sitemapGenerator.write();
}
}
}
package com.langgufoeng.test;
import java.util.HashSet;
import java.util.Set;
import org.htmlparser.Node;
import org.htmlparser.NodeFilter;
import org.htmlparser.Parser;
import org.htmlparser.filters.NodeClassFilter;
import org.htmlparser.filters.OrFilter;
import org.htmlparser.tags.LinkTag;
import org.htmlparser.util.NodeList;
import org.htmlparser.util.ParserException;
/**
* 标題: HtmlParserTool.java
* 路徑: com.langgufoeng.test
* 描述: TODO 從獲得的網頁中提取url
* 作者: 郎國峰
* 時間: 2018-1-7 上午10:32:37
* 版本: @version V1.0
*/
public class HtmlParserTool {
private HtmlParserTool(){}
private static final HtmlParserTool htmlParserTool = new HtmlParserTool();
/*
* 單例模式
*/
public static HtmlParserTool getInstance() {
return htmlParserTool;
}
/**
* @方法名: extracLinks
* @描述: 擷取一個網站上的連結,filter用來過濾連結
* @作者: 郎國峰
* @時間: 2018-1-7 上午10:48:51
* @return 傳回頁面上解析出來的集合
*/
public static Set<String> extracLinks(String url, LinkFilter filter) {
//建立一個set集合,用來存儲頁面上解析出來的符合标準的url
Set<String> links = new HashSet<String>();
try {
Parser parser = new Parser(url);
parser.setEncoding("UTF-8");
//System.out.println("根據url得到的解析結果"+parser);
//過濾<frame> 标簽的filter
NodeFilter frameFilter = new NodeFilter() {
public boolean accept(Node node) {
if (node.getText().startsWith("frame src=")){
return true;
}
return false;
}
};
//OrFilter 用來設定過濾<a> 标簽和<frame> 标簽
OrFilter linkFilter = new OrFilter(new NodeClassFilter(LinkTag.class), frameFilter);
//得到所有經過過濾的标簽
NodeList list = parser.extractAllNodesThatMatch(linkFilter);
//System.out.println("所有經過過濾的标簽"+list+"\t list長度:"+list.size());
for (int i = 0; i < list.size(); i++) {
Node tag = list.elementAt(i);
//System.out.println(tag+"每一個标簽");
//判斷是否是 <a> 标簽
if (tag instanceof LinkTag) {
LinkTag link = (LinkTag) tag;
//得到a标簽的href
String linkUrl = link.getLink();
//判斷a标簽的href的值是否是本網站的,如果是就添加到links中
if (filter.accept(linkUrl)){
links.add(linkUrl);
}
} else {//<frame>标簽
//提取frame裡的src屬性的連結, 如 <frame src="test.html"/>
String frame = tag.getText();
int start = frame.indexOf("src=");
if (start != -1) {
frame = frame.substring(start);
}
int end = frame.indexOf(" ");
String frameUrl = "";
if (end == -1) {
end = frame.indexOf(">");
if (end - 1 > 5) {
frameUrl = frame.substring(5, end - 1);
}
}
if (filter.accept(frameUrl)) {
links.add(frameUrl);
}
}
}
} catch (ParserException e) {
e.printStackTrace();
}
return links;
}
}
package com.langgufoeng.test;
/**
* 标題: LinkFilter.java
* 路徑: com.langgufoeng.test
* 描述: TODO 定義一個網址過濾器,判斷是否以固定網址開頭
* 作者: 郎國峰
* 時間: 2018-1-8 下午5:05:47
* 版本: @version V1.0
*/
public interface LinkFilter {
public boolean accept(String url);
}
package com.langgufoeng.test;
import java.util.HashSet;
import java.util.Set;
/**
* 标題: LinkQueue.java
* 路徑: com.langgufoeng.test
* 描述: TODO 除了URL隊列之外,在爬蟲過程中,還需要一個資料結構來記錄已經通路過的URL.
* 每當通路一個URL的時候,首先在這個資料結構中進行查找,如果目前URL已經存在,則丢
* 棄它,這個資料結構要有兩個特點:
* ~ 結構中儲存的URL不能重複
* ~ 能夠快速的查找(實際系統中URL的數目非常多,是以要考慮查找性能).
* 針對以上兩點,我們選擇HashSet作為存儲結構
* 作者: 郎國峰
* 時間: 2018-1-7 上午8:58:04
* 版本: @version V1.0
*/
public class LinkQueue {
//已通路的url集合
private static Set visitedUrl = new HashSet();
//待通路的url集合
private static Queue unVisitedUrl = new Queue();
//單例
private final static LinkQueue linkQueue = new LinkQueue();
private LinkQueue(){}
public static LinkQueue getInstance(){
return linkQueue;
}
/**
* @方法名: getUnVisitedUrl
* @描述: 獲得将要通路的URL隊列
* @作者: 郎國峰
* @時間: 2018-1-7 上午9:06:21
* @return
*/
public static Queue getUnVisitedUrl(){
return unVisitedUrl;
}
/**
* @方法名: addVisitedUrl
* @描述: 添加到通路過的URL隊列中
* @作者: 郎國峰
* @時間: 2018-1-7 上午9:07:47
* @param url
*/
public static void addVisitedUrl(String url){
visitedUrl.add(url);
}
/**
* @方法名: removeVisitedUrl
* @描述: 移出通路過的url
* @作者: 郎國峰
* @時間: 2018-1-7 上午9:09:40
* @param url
*/
public static void removeVisitedUrl(String url){
visitedUrl.remove(url);
}
/**
* @方法名: unVisitedUrlDeQueue
* @描述: 未通路過的url出隊列
* @作者: 郎國峰
* @時間: 2018-1-7 上午9:12:20
* @return
*/
public static Object unVisitedUrlDeQueue(){
return unVisitedUrl.deQueue();
}
/**
* @方法名: addUnvisitedUrl
* @描述: 保證每個url隻被通路一次
* @作者: 郎國峰
* @時間: 2018-1-7 上午9:19:26
* @param url
*/
public static void addUnvisitedUrl(String url){
if(url != null && !url.trim().equals("") //url不為null也不為空
&& !visitedUrl.contains(url) //url未被通路過
&& !unVisitedUrl.contians(url)){ //url隊列裡不包含此url
unVisitedUrl.enQueue(url); //将此url添加到url隊列中
}
}
/**
* @方法名: getVisitedUrlNum
* @描述: 獲得已經通路過的URL數目
* @作者: 郎國峰
* @時間: 2018-1-7 上午9:21:02
* @return
*/
public static int getVisitedUrlNum(){
return visitedUrl.size();
}
/**
* @方法名: unVisitedUrlsEmpty
* @描述: 判斷未通路過的url是否為空
* @作者: 郎國峰
* @時間: 2018-1-7 上午9:24:03
* @return
*/
public static boolean unVisitedUrlsEmpty(){
return unVisitedUrl.isQueueEmpty();
}
/**
* @方法名: getVisitedUrl
* @描述: 獲得已經通路過的URL集合
* @作者: 郎國峰
* @時間: 2018-1-7 下午3:23:46
* @return
*/
public static Set getVisitedUrl() {
return visitedUrl;
}
}
package com.langgufoeng.test;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import com.langgufoeng.test.entity.UrlEntity;
import com.langgufoeng.test.entity.UrlsetEntity;
/**
* 标題: MainCrawler.java
* 路徑: com.langgufoeng.test
* 描述: TODO 生成sitemap.xml主程式
* 作者: 郎國峰
* 時間: 2018-1-7 下午1:52:36
* 版本: @version V1.0
*/
public class MainCrawler {
private LinkQueue linkQueue = LinkQueue.getInstance();
/**
* @方法名: initCrawlerWithSeeds
* @描述: 使用種子初始化RUL隊列
* @作者: 郎國峰
* @時間: 2018-1-7 下午1:53:13
* @param seeds
*/
private void initCrawlerWithSeeds(String[] seeds) {
for(int i=0; i<seeds.length; i++) {
linkQueue.addUnvisitedUrl(seeds[i]);
}
}
/**
* @方法名: crawling
* @描述: 根據提供的種子進行抓取所有頁面
* @作者: 郎國峰
* @時間: 2018-1-7 下午1:56:17
* @param seeds
*/
public void crawling(String[] seeds) {
//定義過濾器,提取以 www.baidu.com 開始的連結
LinkFilter filter = new LinkFilter() {
public boolean accept(String url) {
if(url.startsWith("http://www.baidu.com")) {
return true;
}
return false;
}
};
int i = 0;
//初始化URL隊列
initCrawlerWithSeeds(seeds);
//循環抓取頁面 循環條件:待抓取的連結不空且抓取的頁面不超過1000個
while(!linkQueue.unVisitedUrlsEmpty() && linkQueue.getVisitedUrlNum()<=1000) {
//隊頭URL出隊列
String visitUrl = (String) linkQueue.unVisitedUrlDeQueue();
if(visitUrl == null) { //如果沒有未被通路過的url,将終止循環
continue;
}
System.out.println("url:"+visitUrl);
DownLoadFile downLoader = new DownLoadFile();
//下載下傳網頁
//downLoader.downloadFile(visitUrl);
//将該url放入已通路的url中
linkQueue.addVisitedUrl(visitUrl);
//提取出下載下傳網頁中的url
Set<String> links = HtmlParserTool.extracLinks(visitUrl, filter);
System.out.println("再次提取的頁面路徑"+links);
for(String link:links) {
linkQueue.addUnvisitedUrl(link);
}
System.err.println("-------------------------------循環"+ ++i+"次------------------------------");
}
}
public static void main(String[] args) {
//聲明一個網站位址字元串
//建立一個爬蟲主線程
MainCrawler crawler = new MainCrawler();
crawler.crawling(new String[]{"http://www.baidu.com"}); //NewFile main
//将所有通路過的連結放到一個set裡
Set set = LinkQueue.getVisitedUrl();
//建立一個list用來存儲sitemap資訊
List<UrlEntity> list = new ArrayList<UrlEntity>();
//周遊所有通路過的連結,得到對應的sitemap資訊,指派給url實體
for (Object object : set) {
UrlEntity url = new UrlEntity();
url.setLoc(object.toString());
list.add(url);
}
//将url實體集合指派給urlset實體
UrlsetEntity.setList(list);
//建立sitemap檔案
EntityToSitemap.toSitemap();
}
}
package com.langgufoeng.test;
import java.util.LinkedList;
/**
* 标題: Queue.java
* 路徑: com.langgufoeng.test
* 描述: TODO 隊列,儲存将要通路的URL
* 作者: 郎國峰
* 時間: 2018-1-7 上午8:49:29
* 版本: @version V1.0
*/
public class Queue {
//使用連結清單實作隊列
private LinkedList queue = new LinkedList();
/**
* @方法名: enQueue
* @描述: 入隊列
* @作者: 郎國峰
* @時間: 2018-1-7 上午8:52:02
* @param t
*/
public void enQueue(Object t){
queue.addLast(t);
}
/**
* @方法名: deQueue
* @描述: 出隊列
* @作者: 郎國峰
* @時間: 2018-1-7 上午8:53:13
* @return
*/
public Object deQueue(){
return queue.removeFirst();
}
/**
* @方法名: isQueueEmpty
* @描述: 判斷隊列是否為空
* @作者: 郎國峰
* @時間: 2018-1-7 上午8:54:17
* @return
*/
public boolean isQueueEmpty(){
return queue.isEmpty();
}
/**
* @方法名: contians
* @描述: 判斷隊列是否包含t
* @作者: 郎國峰
* @時間: 2018-1-7 上午8:55:35
* @param t
* @return
*/
public boolean contians(Object t){
return queue.contains(t);
}
}
以上代碼是我自己寫的一個例子,用的是myeclipse,測試的百度首頁,解析的連接配接知識a标簽和frame标簽的連接配接,如果項目需要可以參考,然後按實際需求進行相應的更改,可能存在一定問題,歡迎批評指正.