Berbicara tentang perayap, menggunakan URLConnection yang disertakan dengan Java dapat mencapai beberapa fungsi perayapan laman dasar, namun untuk beberapa fungsi lebih lanjut, seperti pemrosesan pengalihan dan penghapusan tag HTML, menggunakan URLConnection saja tidak cukup.
Di sini kita dapat menggunakan paket jar pihak ketiga HttpClient.
Selanjutnya, kami menggunakan HttpClient untuk menulis demo yang merayapi ke Baidu:
impor java.io.FileOutputStream;
impor java.io.InputStream;
impor java.io.OutputStream;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.methods.GetMethod;
/**
*
* @penulis CallMeMengapa
*
*/
laba-laba kelas publik {
HttpClient statis pribadi httpClient = HttpClient baru();
/**
* jalur @param
* Tautan ke halaman target
* @return Mengembalikan nilai Boolean, yang menunjukkan apakah halaman target diunduh secara normal
* @throwsException
* Pengecualian IO saat membaca aliran halaman web atau menulis aliran file lokal
*/
halaman unduhan boolean statis publik (jalur string) memunculkan Pengecualian {
//Definisikan aliran input dan output
InputStream masukan = nol;
OutputStream keluaran = null;
// Dapatkan metode postingan
GetMethod getMethod = GetMethod baru(jalur);
//Jalankan, kembalikan kode status
int statusCode = httpClient.executeMethod(getMethod);
// Memproses kode status
// Untuk mempermudah, hanya kode status dengan nilai kembalian 200 yang diproses
jika (statusCode == HttpStatus.SC_OK) {
masukan = getMethod.getResponseBodyAsStream();
// Dapatkan nama file dari URL
Nama file string = path.substring(path.lastIndexOf('/') + 1)
+ ".html";
// Dapatkan aliran keluaran file
keluaran = FileOutputStream baru(nama file);
// Keluaran ke file
int tempByte = -1;
while ((tempByte = masukan.baca()) > 0) {
keluaran.tulis(tempByte);
}
// Tutup aliran masukan
jika (masukan != nol) {
masukan.tutup();
}
// Tutup aliran keluaran
jika (keluaran != nol) {
keluaran.close();
}
kembali benar;
}
kembali salah;
}
public static void main(String[] args) {
mencoba {
// Ambil beranda dan keluaran Baidu
Spider.downloadPage("http://www.baidu.com");
} tangkapan (Pengecualian e) {
e.printStackTrace();
}
}
}
Namun, crawler dasar seperti itu tidak dapat memenuhi kebutuhan berbagai crawler.
Pertama-tama mari kita perkenalkan perayap yang mengutamakan luasnya.
Saya yakin semua orang sudah familiar dengan breadth-first. Secara sederhana, Anda dapat memahami crawler breadth-first seperti ini.
Kami menganggap Internet sebagai grafik berarah super besar. Setiap tautan pada halaman web adalah tepi terarah, dan setiap file atau halaman murni tanpa tautan adalah titik akhir grafik:
Perayap pertama luasnya adalah perayap yang merayapi grafik berarah ini, mulai dari simpul akar dan merayapi data simpul baru lapis demi lapis.
Algoritma penjelajahan lebar adalah sebagai berikut:
(1) Vertex V dimasukkan ke dalam antrian.
(2) Lanjutkan eksekusi ketika antrian tidak kosong, jika tidak maka algoritma akan kosong.
(3) Dequeue, dapatkan simpul kepala V, kunjungi simpul V dan tandai bahwa V telah dikunjungi.
(4) Temukan titik puncak pertama yang berdekatan dari titik V.
(5) Jika kol titik bertetangga di V belum dikunjungi, maka kol dimasukkan ke dalam antrian.
(6) Lanjutkan pencarian titik-titik lain yang bertetangga di kolom V, dan lanjutkan ke langkah (5). Jika semua titik bertetangga di V telah dikunjungi, lanjutkan ke langkah (2).
Menurut algoritma width traversal, urutan traversal gambar di atas adalah: A->B->C->D->E->F->H->G->I, sehingga dilintasi lapis demi lapis .
Perayap pertama luas sebenarnya merayapi serangkaian node benih, yang pada dasarnya sama dengan penjelajahan grafik.
Kita dapat meletakkan URL halaman yang perlu dirayapi di tabel TODO, dan halaman yang dikunjungi di tabel Visited:
Proses dasar crawler luasnya adalah sebagai berikut:
(1) Bandingkan link yang diparsing dengan link pada tabel Visited. Jika link tersebut tidak ada pada tabel Visited, berarti belum dikunjungi.
(2) Masukkan link ke dalam tabel TODO.
(3) Setelah diproses, dapatkan link dari tabel TODO dan langsung masukkan ke tabel Visited.
(4) Lanjutkan proses di atas untuk halaman web yang diwakili oleh tautan ini. Dan sebagainya.
Selanjutnya kita akan membuat crawler luasnya terlebih dahulu selangkah demi selangkah.
Pertama, rancang struktur data untuk menyimpan tabel TODO. Mengingat kebutuhan first-in-first-out, kami menggunakan antrian dan menyesuaikan kelas Quere:
impor java.util.LinkedList;
/**
* Kelas antrian khusus untuk menyimpan tabel TODO
*/
Antrean kelas publik {
/**
* Tentukan antrian dan implementasikan menggunakan LinkedList
*/
antrian LinkedList<Objek> pribadi = LinkedList<Objek>(); // Antrian
/**
* Tambahkan t ke antrian
*/
public void enQueue(Objek t) {
antrian.addLast(t);
}
/**
* Hapus item pertama dari antrian dan kembalikan
*/
deQueue Objek publik() {
kembali antrian.removeFirst();
}
/**
* Mengembalikan apakah antrian kosong
*/
boolean publik isQueueEmpty() {
kembali antrian.isEmpty();
}
/**
* Tentukan dan kembalikan apakah antrian berisi t
*/
contians boolean publik(Objek t) {
return queue.contains(t);
}
/**
* Tentukan dan kembalikan apakah antrian kosong
*/
boolean publik kosong() {
kembali antrian.isEmpty();
}
}
Diperlukan juga struktur data untuk mencatat URL yang telah dikunjungi, yaitu tabel Visited.
Mengingat peran tabel ini, setiap kali URL diakses, URL tersebut dicari terlebih dahulu dalam struktur data ini. Jika URL saat ini sudah ada, tugas URL akan dibuang.
Struktur data ini harus non-duplikat dan dapat dicari dengan cepat, sehingga HashSet dipilih untuk penyimpanan.
Singkatnya, kita membuat kelas SpiderQueue lain untuk menyimpan tabel Visited dan tabel TODO:
impor java.util.HashSet;
impor java.util.Set;
/**
* Kelas khusus untuk menyimpan tabel yang Dikunjungi dan tabel yang belum Dikunjungi
*/
SpiderQueue kelas publik {
/**
* Kumpulan url yang dikunjungi yaitu tabel yang dikunjungi
*/
private static Set<Object> VisitUrl = new HashSet<>();
/**
* Tambahkan ke antrian URL yang dikunjungi
*/
public static void addVisitedUrl(String url) {
mengunjungiUrl.add(url);
}
/**
* Hapus URL yang dikunjungi
*/
public static void hapusVisitedUrl(String url) {
mengunjungiUrl.hapus(url);
}
/**
* Dapatkan jumlah URL yang dikunjungi
*/
public static int getVisitedUrlNum() {
kembalikan VisitUrl.size();
}
/**
* Kumpulan url yang akan dikunjungi, yaitu tabel yang belum dikunjungi
*/
Antrean statis pribadi unVisitedUrl = Antrean baru();
/**
* Dapatkan antrian yang Belum Dikunjungi
*/
Antrean statis publik getUnVisitedUrl() {
kembalikan unVisitedUrl;
}
/**
* UnvisitedUrl yang belum dikunjungi dihapuskan dari antrean
*/
Objek statis publik unVisitedUrlDeQueue() {
kembalikan unVisitedUrl.deQueue();
}
/**
* Pastikan setiap URL hanya dikunjungi satu kali saat menambahkan url ke unVisitedUrl
*/
public static void addUnvisitedUrl(String url) {
if (url != null && !url.trim().equals("") && !visitedUrl.contains(url)
&& !unVisitedUrl.contians(url))
unVisitedUrl.enQueue(url);
}
/**
* Tentukan apakah antrian URL yang belum dikunjungi kosong
*/
boolean statis publik unVisitedUrlsEmpty() {
kembalikan unVisitedUrl.empty();
}
}
Di atas adalah enkapsulasi beberapa kelas khusus. Langkah selanjutnya adalah mendefinisikan kelas alat untuk mengunduh halaman web. Kami mendefinisikannya sebagai kelas DownTool:
pengontrol paket;
import java.io.*;
import org.apache.commons.httpclient.*;
import org.apache.commons.httpclient.methods.*;
import org.apache.commons.httpclient.params.*;
kelas publik DownTool {
/**
* Hasilkan nama file halaman web yang akan disimpan berdasarkan URL dan jenis halaman web, dan hapus karakter nama non-file di URL
*/
String pribadi getFileNameByUrl(String url, String contentType) {
// Hapus tujuh karakter "http://"
url = url.substring(7);
// Konfirmasikan bahwa halaman yang diambil bertipe teks/html
if (contentType.indexOf("html") != -1) {
// Ubah semua simbol khusus di URL menjadi garis bawah
url = url.replaceAll("[//?/:*|<>/"]", "_") + ".html";
} kalau tidak {
url = url.replaceAll("[//?/:*|<>/"]", "_") + "."
+ contentType.substring(contentType.lastIndexOf("/") + 1);
}
kembalikan url;
}
/**
* Simpan array byte halaman web ke file lokal, filePath adalah alamat relatif file yang akan disimpan
*/
private void saveToLocal(byte[] data, String filePath) {
mencoba {
DataOutputStream keluar = DataOutputStream baru(FileOutputStream baru(
File baru(Jalur File)));
untuk (int i = 0; i < data.panjang; i++)
keluar.tulis(data[i]);
keluar.flush();
keluar.dekat();
} tangkapan (IOException e) {
e.printStackTrace();
}
}
// Unduh halaman web yang ditunjuk oleh URL
File unduhan String publik(String url) {
String filePath = nol;
// 1. Hasilkan objek HttpClinet dan atur parameter
HttpClient httpClient = HttpClient baru();
//Setel batas waktu koneksi HTTP 5 detik
httpKlien.getHttpConnectionManager().getParams()
.setConnectionTimeout(5000);
// 2. Hasilkan objek GetMethod dan atur parameter
GetMethod getMethod = GetMethod baru(url);
//Setel batas waktu permintaan pengambilan menjadi 5 detik
getMethod.getParams().setParameter(HttpMethodParams.SO_TIMEOUT, 5000);
//Atur pemrosesan permintaan percobaan ulang
getMethod.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,
DefaultHttpMethodRetryHandler());
// 3. Jalankan permintaan GET
mencoba {
int statusCode = httpClient.executeMethod(getMethod);
// Tentukan kode status akses
jika (kode status!= HttpStatus.SC_OK) {
System.err.println("Metode gagal: "
+ getMethod.getStatusLine());
filePath = nol;
}
// 4. Memproses konten respons HTTP
byte[] responBody = getMethod.getResponseBody(); // Dibaca sebagai array byte
// Hasilkan nama file saat menyimpan berdasarkan URL halaman web
filePath = "temp//"
+ getFileNameByUrl(url,
getMethod.getResponseHeader("Tipe Konten")
.getValue());
saveToLocal(responseBody, filePath);
} tangkapan (HttpException e) {
//Terjadi pengecualian fatal. Mungkin protokolnya salah atau ada yang salah dengan konten yang dikembalikan.
System.out.println("Silakan periksa apakah alamat http Anda sudah benar");
e.printStackTrace();
} tangkapan (IOException e) {
//Terjadi pengecualian jaringan
e.printStackTrace();
} Akhirnya {
// Lepaskan koneksi
getMethod.releaseConnection();
}
kembalikan jalur file;
}
}
Di sini kita memerlukan kelas HtmlParserTool untuk menangani tag Html:
pengontrol paket;
impor java.util.HashSet;
impor 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;
impor model.LinkFilter;
kelas publik HtmlParserTool {
// Mendapatkan link pada suatu website, filter digunakan untuk memfilter link
public static Set<String> extracLinks(String url, filter LinkFilter) {
Setel tautan<String> = HashSet<String>();
mencoba {
Parser parser = Parser baru(url);
parser.setEncoding("gb2312");
// Filter tag <frame>, digunakan untuk mengekstrak atribut src di tag frame
NodeFilter frameFilter = NodeFilter baru() {
serialVersionUID panjang akhir statis pribadi = 1L;
@Mengesampingkan
boolean publik menerima(Node node) {
if (node.getText().startsWith("frame src=")) {
kembali benar;
} kalau tidak {
kembali salah;
}
}
};
// OrFilter untuk mengatur filter tag <a> dan tag <frame>
OrFilter linkFilter = OrFilter baru(NodeClassFilter baru(
LinkTag.kelas), frameFilter);
// Dapatkan semua tag yang difilter
Daftar NodeList = parser.extractAllNodesThatMatch(linkFilter);
untuk (int i = 0; i < daftar.ukuran(); i++) {
Tag simpul = list.elementAt(i);
if (tag instanceof LinkTag)// tag <a>
{
Tautan LinkTag = tag (Tag Tautan);
String linkUrl = link.getLink();// URL
if (filter.accept(linkUrl))
link.add(linkUrl);
} else// tag <bingkai>
{
// Ekstrak link atribut src di dalam frame, misalnya <frame src="test.html"/>
Bingkai string = tag.getText();
int mulai = frame.indexOf("src=");
frame = frame.substring(mulai);
int end = frame.indexOf(" ");
jika (akhir == -1)
end = frame.indexOf(">");
String frameUrl = frame.substring(5, akhir - 1);
if (filter.accept(frameUrl))
link.add(frameUrl);
}
}
} tangkapan (ParserException e) {
e.printStackTrace();
}
tautan balik;
}
}
Terakhir, mari kita tulis kelas crawler untuk memanggil kelas dan fungsi enkapsulasi sebelumnya:
pengontrol paket;
impor java.util.Set;
impor model.LinkFilter;
impor model.SpiderQueue;
BfsSpider kelas publik {
/**
* Inisialisasi antrian URL menggunakan seed
*/
private void initCrawlerWithSeeds(String[] benih) {
for (int i = 0; i < biji.panjang; i++)
SpiderQueue.addUnvisitedUrl(biji[i]);
}
//Tentukan filter untuk mengekstrak tautan yang dimulai dengan http://www.xxxx.com
perayapan kekosongan publik (String[] benih) {
Filter LinkFilter = LinkFilter baru() {
penerimaan boolean publik(String url) {
jika (url.startsWith("http://www.baidu.com"))
kembali benar;
kalau tidak
kembali salah;
}
};
//Inisialisasi antrian URL
initCrawlerWithSeeds(biji);
// Kondisi loop: link yang akan dirayapi tidak kosong dan jumlah halaman web yang dirayapi tidak lebih dari 1000
sementara (!SpiderQueue.unVisitedUrlsEmpty()
&& SpiderQueue.getVisitedUrlNum() <= 1000) {
//URL kepala antrian di-dequeue
String visitUrl = (String) SpiderQueue.unVisitedUrlDeQueue();
jika (visitUrl == null)
melanjutkan;
DownTool downLoader = DownTool baru();
// Unduh halaman web
downLoader.downloadFile(visitUrl);
// Masukkan URL ini ke dalam URL yang dikunjungi
SpiderQueue.addVisitedUrl(visitUrl);
//Ekstrak URL dari halaman web unduhan
Setel tautan<String> = HtmlParserTool.extracLinks(visitUrl, filter);
// URL baru yang belum dikunjungi dimasukkan dalam antrean
untuk (Tautan string : tautan) {
SpiderQueue.addUnvisitedUrl(tautan);
}
}
}
//entri metode utama
public static void main(String[] args) {
Perayap BfsSpider = BfsSpider baru();
crawler.crawling(String baru[] { "http://www.baidu.com" });
}
}
Setelah menjalankannya, Anda dapat melihat bahwa crawler telah merayapi semua halaman di halaman web Baidu:
Di atas adalah keseluruhan konten java yang menggunakan toolkit HttpClient dan crawler lebar untuk meng-crawl konten, jadi teman-teman harus memikirkannya baik-baik.