عند الحديث عن برامج الزحف، فإن استخدام URLConnection الذي يأتي مع Java يمكن أن يحقق بعض وظائف الزحف الأساسية للصفحة، ولكن بالنسبة لبعض الوظائف الأكثر تقدمًا، مثل معالجة إعادة التوجيه وإزالة علامات HTML، فإن مجرد استخدام URLConnection ليس كافيًا.
هنا يمكننا استخدام حزمة HttpClient التابعة لجهة خارجية.
بعد ذلك، نستخدم HttpClient لكتابة عرض توضيحي يزحف إلى Baidu:
import java.io.FileOutputStream;
استيراد java.io.InputStream؛
استيراد java.io.OutputStream؛
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.methods.GetMethod;
/**
*
* @author CallMeWhen
*
*/
عنكبوت الطبقة العامة {
خاص ثابت HttpClient httpClient = new HttpClient();
/**
* @ مسار المعلمة
* رابط إلى الصفحة المستهدفة
* @return يُرجع قيمة منطقية، تشير إلى ما إذا كان يتم تنزيل الصفحة المستهدفة بشكل طبيعي أم لا
* @throwsException
* استثناء الإدخال والإخراج عند قراءة دفق صفحة الويب أو كتابة دفق ملف محلي
*/
صفحة التنزيل المنطقية العامة الثابتة (مسار السلسلة) تطرح استثناءً {
// تحديد تدفقات الإدخال والإخراج
InputStream input = null;
OutputStreamput = null;
// احصل على طريقة النشر
GetMethod getMethod = new GetMethod(path);
// تنفيذ، وإرجاع رمز الحالة
int StatusCode = httpClient.executeMethod(getMethod);
// معالجة رمز الحالة
// للتبسيط، تتم معالجة رمز الحالة بقيمة الإرجاع 200 فقط
إذا (رمز الحالة == HttpStatus.SC_OK) {
input = getMethod.getResponseBodyAsStream();
// احصل على اسم الملف من عنوان URL
اسم ملف السلسلة = path.substring(path.lastIndexOf('/') + 1)
+ ".html"؛
// احصل على دفق إخراج الملف
Output = new FileOutputStream(filename);
// الإخراج إلى الملف
int tempByte = -1;
بينما ((tempByte = input.read()) > 0) {
input.write(tempByte);
}
// أغلق دفق الإدخال
إذا (الإدخال!= فارغة) {
input. Close();
}
// أغلق دفق الإخراج
إذا (الإخراج! = فارغ) {
الإخراج. إغلاق ()؛
}
عودة صحيحة؛
}
عودة كاذبة.
}
public static void main(String[] args) {
يحاول {
// احصل على صفحة بايدو الرئيسية والمخرجات
Spider.downloadPage("http://www.baidu.com");
} قبض (الاستثناء ه) {
printStackTrace();
}
}
}
ومع ذلك، لا يمكن لمثل هذا الزاحف الأساسي تلبية احتياجات برامج الزحف المختلفة.
دعونا أولاً نقدم الزاحف ذو العرض الأول.
أعتقد أن الجميع على دراية بالعرض أولاً، بعبارات بسيطة، يمكنك فهم برامج الزحف ذات العرض الأول مثل هذا.
نحن نعتبر الإنترنت بمثابة رسم بياني موجه كبير جدًا، فكل رابط على صفحة الويب يمثل حافة موجهة، وكل ملف أو صفحة خالصة بدون رابط هي نقطة نهاية الرسم البياني:
الزاحف ذو العرض الأول هو مثل هذا الزاحف، فهو يزحف على هذا الرسم البياني الموجه، بدءًا من العقدة الجذرية ويزحف إلى بيانات العقد الجديدة طبقة تلو الأخرى.
خوارزمية اجتياز العرض هي كما يلي:
(1) تم وضع Vertex V في قائمة الانتظار.
(2) تابع التنفيذ عندما لا تكون قائمة الانتظار فارغة، وإلا فإن الخوارزمية فارغة.
(3) قم بإنهاء قائمة الانتظار، واحصل على العقدة الرئيسية V، وقم بزيارة قمة الرأس V وقم بوضع علامة على أنه تمت زيارة V.
(4) ابحث عن العمود المجاور الأول للقمة V.
(5) إذا لم تتم زيارة العمود الرأسي المجاور لـ V، فسيتم وضع العمود في قائمة الانتظار.
(6) استمر في البحث عن القمم المجاورة الأخرى لـ V، وانتقل إلى الخطوة (5) إذا تمت زيارة جميع القمم المجاورة لـ V، انتقل إلى الخطوة (2).
وفقًا لخوارزمية اجتياز العرض، فإن ترتيب الاجتياز للصورة أعلاه هو: A->B->C->D->E->F->H->G->I، بحيث يتم اجتيازها طبقة تلو الأخرى .
يقوم زاحف العرض الأول في الواقع بالزحف إلى سلسلة من العقد الأولية، وهو في الأساس نفس اجتياز الرسم البياني.
يمكننا وضع عناوين URL للصفحات التي يجب الزحف إليها في جدول المهام، والصفحات التي تمت زيارتها في جدول الزيارة:
العملية الأساسية للزاحف ذو العرض الأول هي كما يلي:
(1) قارن الرابط الذي تم تحليله بالرابط الموجود في جدول الزيارة. إذا كان الرابط غير موجود في جدول الزيارة، فهذا يعني أنه لم تتم زيارته.
(2) ضع الرابط في جدول المهام.
(3) بعد المعالجة، احصل على رابط من جدول المهام وضعه مباشرة في جدول الزيارة.
(4) تابع العملية المذكورة أعلاه لصفحة الويب التي يمثلها هذا الرابط. وهكذا.
بعد ذلك، سنقوم بإنشاء زاحف العرض الأول خطوة بخطوة.
أولاً، قم بتصميم بنية بيانات لتخزين جدول TODO مع الأخذ في الاعتبار الحاجة إلى ما يدخل أولاً يخرج أولاً، نستخدم قائمة انتظار ونخصص فئة Quere:
import java.util.LinkedList;
/**
* فئة قائمة انتظار مخصصة لحفظ جدول TODO
*/
قائمة انتظار الطبقة العامة {
/**
* تحديد قائمة الانتظار وتنفيذها باستخدام LinkedList
*/
Private LinkedList<Object> queue = new LinkedList<Object>();
/**
* أضف ر إلى قائمة الانتظار
*/
public void enQueue(Object t) {
queue.addLast(t);
}
/**
* قم بإزالة العنصر الأول من قائمة الانتظار وإعادته
*/
كائن عام deQueue () {
return queue.removeFirst();
}
/**
* إرجاع ما إذا كانت قائمة الانتظار فارغة
*/
المنطقية العامة isQueueEmpty() {
إرجاع قائمة الانتظار.isEmpty();
}
/**
* تحديد وإرجاع ما إذا كانت قائمة الانتظار تحتوي على t
*/
contians المنطقية العامة (كائن t) {
قائمة انتظار العودة. يحتوي على (ر)؛
}
/**
* تحديد وإرجاع ما إذا كانت قائمة الانتظار فارغة
*/
منطقية عامة فارغة () {
إرجاع قائمة الانتظار.isEmpty();
}
}
هناك حاجة أيضًا إلى بنية بيانات لتسجيل عناوين URL التي تمت زيارتها، أي جدول الزيارة.
مع الأخذ في الاعتبار دور هذا الجدول، عندما يتم الوصول إلى عنوان URL، يتم البحث عنه أولاً في بنية البيانات هذه. إذا كان عنوان URL الحالي موجودًا بالفعل، فسيتم تجاهل مهمة URL.
يجب أن تكون بنية البيانات هذه غير مكررة ويمكن البحث عنها بسرعة، لذلك يتم اختيار HashSet للتخزين.
لتلخيص ذلك، نقوم بإنشاء فئة SpiderQueue أخرى لحفظ الجدول الذي تمت زيارته وجدول المهام:
import java.util.HashSet;
import java.util.Set;
/**
* فئة مخصصة لحفظ الجدول الذي تمت زيارته والجدول الذي لم تتم زيارته
*/
الطبقة العامة SpiderQueue {
/**
* مجموعة عناوين URL التي تمت زيارتها، وهي الجدول الذي تمت زيارته
*/
مجموعة ثابتة خاصة <Object> VisitUrl = new HashSet<>();
/**
* أضف إلى قائمة انتظار URL التي تمت زيارتها
*/
public static void addVisitedUrl(String url) {
VisitUrl.add(url);
}
/**
* إزالة عناوين URL التي تمت زيارتها
*/
إزالة الفراغ الثابت العام (String url) {
VisitUrl.remove(url);
}
/**
* احصل على عدد عناوين URL التي تمت زيارتها
*/
int العام الثابت getVisitedUrlNum() {
إرجاع تمت زيارتهUrl.size();
}
/**
* مجموعة عناوين url التي سيتم زيارتها، أي الجدول الذي لم تتم زيارته
*/
قائمة الانتظار الثابتة الخاصة unVisitedUrl = new Queue();
/**
* احصل على قائمة الانتظار غير الزيارة
*/
قائمة الانتظار الثابتة العامة getUnVisitedUrl() {
إرجاع unVisitedUrl;
}
/**
* تم وضع unVisitedUrl غير المرغوب فيه في قائمة الانتظار
*/
كائن ثابت عام unVisitedUrlDeQueue() {
إرجاع unVisitedUrl.deQueue();
}
/**
* تأكد من زيارة كل عنوان URL مرة واحدة فقط عند إضافة عنوان url إلى unVisitedUrl
*/
public static void addUnvisitedUrl(String url) {
إذا (url != null && !url.trim().equals("") && !visitedUrl.contains(url)
&& !unVisitedUrl.contians(url))
unVisitedUrl.enQueue(url);
}
/**
* تحديد ما إذا كانت قائمة انتظار URL غير التي تمت زيارتها فارغة
*/
منطقية عامة ثابتة unVisitedUrlsEmpty() {
إرجاع unVisitedUrl.empty();
}
}
ما ورد أعلاه عبارة عن تغليف لبعض الفئات المخصصة، والخطوة التالية هي تحديد فئة أداة لتنزيل صفحات الويب، ونحددها على أنها فئة DownTool:
وحدة تحكم الحزمة؛
استيراد java.io.*;
import org.apache.commons.httpclient.*;
import org.apache.commons.httpclient.methods.*;
import org.apache.commons.httpclient.params.*;
الطبقة العامة DownTool {
/**
* قم بإنشاء اسم ملف صفحة الويب المراد حفظها بناءً على عنوان URL ونوع صفحة الويب، وقم بإزالة الأحرف غير الموجودة في اسم الملف في عنوان URL
*/
سلسلة خاصة getFileNameByUrl(String url, String contentType) {
// قم بإزالة الأحرف السبعة "http://"
url = url.substring(7);
// تأكد من أن الصفحة التي تم التقاطها هي من نوع text/html
إذا (contentType.indexOf("html") != -1) {
// تحويل جميع الرموز الخاصة في عناوين URL إلى شرطة سفلية
url = url.replaceAll("[//?/:*|<>/"]", "_") + ".html";
} آخر {
url = url.replaceAll("[//?/:*|<>/"]"، "_") + "."
+ contentType.substring(contentType.lastIndexOf("/") + 1);
}
عنوان URL للإرجاع؛
}
/**
* احفظ مصفوفة بايت صفحة الويب في ملف محلي، filePath هو العنوان النسبي للملف المراد حفظه
*/
باطلة خاصة saveToLocal (بايت [] البيانات، سلسلة filePath) {
يحاول {
DataOutputStream out = DataOutputStream الجديد (new FileOutputStream(
ملف جديد(filePath)));
لـ (int i = 0; i < data. length; i++)
out.write(data[i]);
out.flush();
out.Close();
} قبض (IOException ه) {
printStackTrace();
}
}
// قم بتنزيل صفحة الويب المشار إليها بواسطة عنوان URL
ملف تنزيل السلسلة العامة (سلسلة عنوان url) {
String filePath = null;
// 1. قم بإنشاء كائن HttpClinet وقم بتعيين المعلمات
HttpClient httpClient = new HttpClient();
// قم بتعيين مهلة اتصال HTTP على 5 ثوانٍ
httpClient.getHttpConnectionManager().getParams()
.setConnectionTimeout(5000);
// 2. قم بإنشاء كائن GetMethod وقم بتعيين المعلمات
GetMethod getMethod = new GetMethod(url);
// اضبط مهلة طلب الحصول على 5 ثوانٍ
getMethod.getParams().setParameter(HttpMethodParams.SO_TIMEOUT, 5000);
// قم بتعيين إعادة محاولة معالجة الطلب
getMethod.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,
new DefaultHttpMethodRetryHandler());
// 3. تنفيذ طلب الحصول على
يحاول {
int StatusCode = httpClient.executeMethod(getMethod);
// تحديد رمز حالة الوصول
إذا (رمز الحالة! = HttpStatus.SC_OK) {
System.err.println("فشلت الطريقة:"
+ getMethod.getStatusLine());
filePath = null;
}
// 4. معالجة محتوى استجابة HTTP
byte[] ResponseBody = getMethod.getResponseBody();
// أنشئ اسم الملف عند الحفظ بناءً على عنوان URL لصفحة الويب
مسار الملف = "درجة الحرارة //"
+ getFileNameByUrl(url,
getMethod.getResponseHeader("نوع المحتوى")
.getValue());
saveToLocal(responseBody, filePath);
} قبض على (HttpException e) {
// يحدث استثناء فادح. قد يكون البروتوكول غير صحيح أو أن هناك خطأ ما في المحتوى الذي تم إرجاعه.
System.out.println("الرجاء التحقق مما إذا كان عنوان http الخاص بك صحيحًا");
printStackTrace();
} قبض (IOException ه) {
// يحدث استثناء في الشبكة
printStackTrace();
} أخيراً {
// حرر الاتصال
getMethod.releaseConnection();
}
إرجاع مسار الملف؛
}
}
نحتاج هنا إلى فئة HtmlParserTool للتعامل مع علامات Html:
وحدة تحكم الحزمة؛
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;
import model.LinkFilter;
الطبقة العامة HtmlParserTool {
// احصل على الروابط على موقع ويب، ويتم استخدام الفلتر لتصفية الروابط
مجموعة ثابتة عامة <String> extracLinks(String url, LinkFilter filter) {
Set<String> links = new HashSet<String>();
يحاول {
Parser parser = new Parser(url);
parser.setEncoding("gb2312");
// قم بتصفية علامة <frame> المستخدمة لاستخراج سمة src في علامة الإطار
NodeFilterframeFilter = new NodeFilter() {
serialVersionUID النهائي الطويل الخاص الثابت = 1L؛
@تجاوز
قبول منطقي عام (عقدة العقدة) {
إذا (node.getText().startsWith("frame src =")) {
عودة صحيحة؛
} آخر {
عودة كاذبة.
}
}
};
// OrFilter لتعيين علامة الفلتر <a> وعلامة <frame>
OrFilter linkFilter = new OrFilter(new NodeClassFilter(
LinkTag.class)،frameFilter);
// احصل على جميع العلامات التي تمت تصفيتها
NodeList list = parser.extractAllNodesThatMatch(linkFilter);
لـ (int i = 0; i < list.size(); i++) {
علامة العقدة = list.elementAt(i);
إذا (علامة مثيل LinkTag)// علامة <a>
{
رابط LinkTag = علامة (LinkTag)؛
String linkUrl = link.getLink();// URL
إذا (filter.accept(linkUrl))
links.add(linkUrl);
} else// علامة <frame>
{
// استخرج رابط سمة src في الإطار، مثل <frame src="test.html"/>
إطار السلسلة = tag.getText();
int start =frame.indexOf("src=");
الإطار = الإطار. سلسلة فرعية (بدء)؛
int end =frame.indexOf(" ");
إذا (النهاية == -1)
النهاية = الإطار.indexOf(">");
StringframeUrl =frame.substring(5, end - 1);
إذا (filter.accept(frameUrl))
links.add(frameUrl);
}
}
} قبض على (ParserException e) {
printStackTrace();
}
روابط العودة؛
}
}
أخيرًا، لنكتب فئة زاحف لاستدعاء فئة التغليف السابقة ووظيفتها:
وحدة تحكم الحزمة؛
import java.util.Set;
import model.LinkFilter;
import model.SpiderQueue;
الطبقة العامة BfsSpider {
/**
* تهيئة قائمة انتظار URL باستخدام البذور
*/
الفراغ الخاص initCrawlerWithSeeds(String[] Seeds) {
لـ (int i = 0; i <seeds.length; i++)
SpiderQueue.addUnvisitedUrl(seeds[i]);
}
// حدد عامل تصفية لاستخراج الروابط التي تبدأ بـ http://www.xxxx.com
زحف الفراغ العام (سلسلة [] بذور) {
مرشح LinkFilter = LinkFilter الجديد () {
قبول منطقي عام (سلسلة عنوان URL) {
إذا (url.startsWith("http://www.baidu.com"))
عودة صحيحة؛
آخر
عودة كاذبة.
}
};
// تهيئة قائمة انتظار URL
initCrawlerWithSeeds(seeds);
// حالة التكرار: الرابط المراد الزحف إليه ليس فارغًا وعدد صفحات الويب التي تم الزحف إليها لا يزيد عن 1000
بينما (!SpiderQueue.unVisitedUrlsEmpty()
&& SpiderQueue.getVisitedUrlNum() <= 1000) {
// تم وضع عنوان URL لرأس قائمة الانتظار في قائمة الانتظار
String VisitUrl = (String) SpiderQueue.unVisitedUrlDeQueue();
إذا (زيارةUrl == فارغة)
يكمل؛
DownTool downLoader = new DownTool();
// تنزيل صفحة الويب
downLoader.downloadFile(visitUrl);
// ضع عنوان URL هذا في عنوان URL الذي تمت زيارته
SpiderQueue.addVisitedUrl(visitUrl);
//استخرج عنوان URL من صفحة الويب الخاصة بالتنزيل
Set<String> links = HtmlParserTool.extracLinks(visitUrl, filter);
// يتم وضع عناوين URL الجديدة التي لم تتم زيارتها في قائمة الانتظار
لـ (رابط السلسلة: الروابط) {
SpiderQueue.addUnvisitedUrl(link);
}
}
}
// إدخال الطريقة الرئيسية
public static void main(String[] args) {
BfsSpider Crawler = new BfsSpider();
Crawler.crawling(new String[] { "http://www.baidu.com" });
}
}
بعد تشغيله، يمكنك أن ترى أن الزاحف قد قام بالزحف إلى جميع الصفحات الموجودة ضمن صفحة ويب بايدو:
ما ورد أعلاه هو محتوى Java بأكمله الذي يستخدم مجموعة أدوات HttpClient وزاحف العرض للزحف إلى المحتوى، وهو أكثر تعقيدًا بعض الشيء، لذا يجب على الأصدقاء التفكير فيه بعناية، وآمل أن يكون مفيدًا للجميع.