في هذه المقالة سأكتب زاحف ويب صغير. لم أكن متأكدًا مما إذا كان موقع الويب الخاص بي يحتوي على عناوين صفحات رائعة على مستوى الموقع ، وإذا كان لدي عناوين مكررة ، لذلك كتبت هذه الأداة الصغيرة لمعرفة ذلك.
سأبدأ بكتابة أمر يقبل صفحة بداية من سطر الأوامر ، ويتبع أي رابط يحتوي على عنوان url الأصلي كقاعدة.
سأضيف لاحقًا علامة اختيارية لاكتشاف ما إذا كان الموقع يحتوي على أم لاعناوين مكررة، وهو أمر قد يكون مفيدًا لأغراض تحسين محركات البحث.
مقدمةgolang.org/x/net/html
الgolang.org/x
الحزم هي حزم يحتفظ بها فريق Go ، لكنها ليست جزءًا من المكتبة القياسية لأسباب مختلفة.
ربما تكون محددة للغاية ، ولن يستخدمها غالبية مطوري Go. ربما لا تزال قيد التطوير أو تجريبية ، لذلك لا يمكن تضمينها في stdlib ، والتي يجب أن تفي بوعد Go 1.0 بعدم وجود تغييرات غير متوافقة مع الوراء - عندما يتم إدخال شيء ما في stdlib ، يكون الأمر "نهائيًا".
واحدة من هذه الحزم هيgolang.org/x/net/html
.
لتثبيته ، قم بتنفيذ
go get golang.org/x/net...
في هذه المقالة سأستخدم على وجه الخصوصhtml.Parse()
وظيفة ، وhtml.Node
هيكل:
package html
type Node struct {
Type NodeType
Data string
Attr []Attribute
FirstChild, NextSibling *node
}
type NodeType int32
const (
ErrorNode NodeType = iota
TextNode
DocumentNode
ElementNode
CommentNode
DoctypeNode
)
type Attribute struct {
Key, Val string
}
func Parse(r io.Reader) (*Node, error)
قائمة روابط الموقع وعناوين الصفحات
يقبل البرنامج الأول أدناه عنوان URL ويحسب الروابط الفريدة التي يعثر عليها ، ويعطي ناتجًا مثل هذا:
http://localhost:1313/go-filesystem-structure/ -> Filesystem Structure of a Go project
http://localhost:1313/golang-measure-time/ -> Measuring execution time in a Go program
http://localhost:1313/go-tutorial-fortune/ -> Go CLI tutorial: fortune clone
http://localhost:1313/go-tutorial-lolcat/ -> Build a Command Line app with Go: lolcat
لنبدأ منmain()
، حيث يعرض نظرة عامة عالية المستوى على ما يفعله البرنامج.
- يحصل على
url
من صفحات CLI باستخدام `os.Args [1] - ينشأ
visited
، وهي خريطة تحتوي على سلاسل رئيسية وسلسلة قيمة ، حيث سنخزن عنوان URL وعنوان صفحات الموقع - المكالمات
analyze()
.url
يتم تمريره مرتين ، لأن الوظيفة تكرارية وتعمل المعلمة الثانية كعنوان URL الأساسي للمكالمات العودية يتكرر على
visited
الخريطة التي تم تمريرها بالرجوع إلىanalyze()
والآن تم ملء جميع القيم ، حتى نتمكن من طباعتهاpackage main
import ( “fmt” “net/http” “os” “strings”
<span style="color:#e6db74">"golang.org/x/net/html"</span>
)
func main() { url := os.Args[1] if url == “” { fmt.Println(“Usage:
webcrawler <url>
“) os.Exit(1) } visited := map[string]string{} analyze(url, url, &visited) for k, v := range visited { fmt.Printf(”%s -> %s\n”, k, v) } }
بسيطا بما فيه الكفاية؟ دعنا ندخلanalyze()
. أول شيء ، إنه يدعوparse()
، والذي يعطي سلسلة تشير إلى عنوان URL سيؤدي إلى جلبه وتحليله بإرجاع مؤشر html.Node وخطأ.
تحليل func (سلسلة عنوان url) (* html.Node ، خطأ)
بعد التحقق من النجاح ،analyze()
يجلب عنوان الصفحة باستخدامpageTitle()
، التي تعطي إشارة إلى html.Node ، تفحصها حتى تعثر على علامة العنوان ، ثم ترجع قيمتها.
func pageTitle (n * html.Node) سلسلة
بمجرد حصولنا على عنوان الصفحة ، يمكننا إضافته إلى ملفvisited
خريطة.
بعد ذلك ، نحصل على جميع روابط الصفحات عن طريق الاتصالpageLinks()
، والتي بالنظر إلى عقدة صفحة البداية ، ستفحص بشكل متكرر جميع عقد الصفحة وستعيد قائمة الروابط الفريدة التي تم العثور عليها (لا توجد تكرارات).
func pageLinks (ارتباطات [] سلسلة ، n * html.Node) [] سلسلة
بمجرد أن نحصل على شريحة الروابط ، نكررها ، ونقوم بفحص بسيط: إذاvisited
لا تحتوي على الصفحة بعد ، فهذا يعني أننا لم نقم بزيارتها بعد ، ويجب أن يحتوي الرابطbaseurl
كبادئة. إذا تم تأكيد هذين التأكيدات ، فيمكننا الاتصالanalyze()
مع رابط الرابط.
// analyze given a url and a basurl, recoursively scans the page
// following all the links and fills the `visited` map
func analyze(url, baseurl string, visited *map[string]string) {
page, err := parse(url)
if err != nil {
fmt.Printf("Error getting page %s %s\n", url, err)
return
}
title := pageTitle(page)
(*visited)[url] = title
<span style="color:#75715e">//recursively find links
links := pageLinks(nil, page)
for _, link := range links {
if (*visited)[link] == “” && strings.HasPrefix(link, baseurl) {
analyze(link, baseurl, visited)
}
}
}
pageTitle()
يستخدمgolang.org/x/net/html
واجهات برمجة التطبيقات التي قدمناها أعلاه. في التكرار الأول ،n
هل<html>
العقدة. نحن نبحث عن علامة العنوان. لا يرضي التكرار الأول هذا أبدًا ، لذلك نذهب ونقوم بعمل حلقة فوق الطفل الأول لـ<html>
أولا ، وإخوته فيما بعد ، ونحن ندعوpageTitle()
بشكل متكرر تمرير العقدة الجديدة.
في النهاية سنصل إلى<title>
الوسم: أhtml.Node
على سبيل المثال معType
يساويhtml.ElementNode
(انظر أعلاه) وData
يساويtitle
، ونعيد محتواه من خلال الوصول إلىFirstChild.Data
منشأه
// pageTitle given a reference to a html.Node, scans it until it
// finds the title tag, and returns its value
func pageTitle(n *html.Node) string {
var title string
if n.Type == html.ElementNode && n.Data == "title" {
return n.FirstChild.Data
}
for c := n.FirstChild; c != nil; c = c.NextSibling {
title = pageTitle(c)
if title != "" {
break
}
}
return title
}
pageLinks()
لا يختلف كثيرا عنpageTitle()
، باستثناء أنه لا يتوقف عند العثور على العنصر الأول ، ولكنه يبحث عن كل ارتباط ، لذلك يجب أن نجتازlinks
شريحة كمعامل لهذه الوظيفة العودية. يتم اكتشاف الروابط عن طريق التحقق منhtml.Node
لديهاhtml.ElementNode
Type
وData
يجب أنa
وأيضا يجب أن يكون لديهمAttr
معKey
href
وإلا فإنه يمكن أن يكون مرساة.
// pageLinks will recursively scan a `html.Node` and will return
// a list of links found, with no duplicates
func pageLinks(links []string, n *html.Node) []string {
if n.Type == html.ElementNode && n.Data == "a" {
for _, a := range n.Attr {
if a.Key == "href" {
if !sliceContains(links, a.Val) {
links = append(links, a.Val)
}
}
}
}
for c := n.FirstChild; c != nil; c = c.NextSibling {
links = pageLinks(links, c)
}
return links
}
sliceContains()
هي وظيفة المنفعة التي دعاهاpageLinks()
للتحقق من التفرد في الشريحة.
// sliceContains returns true if `slice` contains `value`
func sliceContains(slice []string, value string) bool {
for _, v := range slice {
if v == value {
return true
}
}
return false
}
الوظيفة الأخيرة هيparse()
. يستخدمhttp
وظيفة stdlib للحصول على محتويات URL (http.Get()
) ثم يستخدم ملفgolang.org/x/net/html
html.Parse()
API لتحليل نص الاستجابة من طلب HTTP ، وإرجاع ملفhtml.Node
المرجعي.
// parse given a string pointing to a URL will fetch and parse it
// returning an html.Node pointer
func parse(url string) (*html.Node, error) {
r, err := http.Get(url)
if err != nil {
return nil, fmt.Errorf("Cannot get page")
}
b, err := html.Parse(r.Body)
if err != nil {
return nil, fmt.Errorf("Cannot parse page")
}
return b, err
}
كشف العناوين المكررة
منذ أن أردتاستخدم علم سطر الأوامرللتحقق من التكرارات ، سأقوم بتغيير طفيف في كيفية تمرير عنوان URL إلى البرنامج: بدلاً من استخدامos.Args
، سأقوم بتمرير عنوان URL باستخدام علامة أيضًا.
هذا هو المعدلmain()
الوظيفة ، مع تحليل الأعلام قبل القيام بالأعمال المعتادة لإعدادanalyze()
تنفيذ وطباعة القيم. بالإضافة إلى ذلك ، يوجد في النهاية فحص لـdup
علم منطقي ، وإذا كان صحيحًا يتم تشغيلهcheckDuplicates()
.
import (
"flag"
//...
)
func main() {
var url string
var dup bool
flag.StringVar(&url, “url”, “”, “the url to parse”)
flag.BoolVar(&dup, “dup”, false, “if set, check for duplicates”)
flag.Parse()
<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">url</span> <span style="color:#f92672">==</span> <span style="color:#e6db74">""</span> {
<span style="color:#a6e22e">flag</span>.<span style="color:#a6e22e">PrintDefaults</span>()
<span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Exit</span>(<span style="color:#ae81ff">1</span>)
}
<span style="color:#a6e22e">visited</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">map</span>[<span style="color:#66d9ef">string</span>]<span style="color:#66d9ef">string</span>{}
<span style="color:#a6e22e">analyze</span>(<span style="color:#a6e22e">url</span>, <span style="color:#a6e22e">url</span>, <span style="color:#f92672">&</span><span style="color:#a6e22e">visited</span>)
<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">link</span>, <span style="color:#a6e22e">title</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">range</span> <span style="color:#a6e22e">visited</span> {
<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Printf</span>(<span style="color:#e6db74">"%s -> %s\n"</span>, <span style="color:#a6e22e">link</span>, <span style="color:#a6e22e">title</span>)
}
<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">dup</span> {
<span style="color:#a6e22e">checkDuplicates</span>(<span style="color:#f92672">&</span><span style="color:#a6e22e">visited</span>)
}
}
checkDuplicates
تأخذ خريطة url -> العناوين وتكرر عليها لبناء موقعها الخاصuniques
الخريطة ، هذه المرة تحتوي على عنوان الصفحة كمفتاح ، حتى نتمكن من التحقق منuniques[title] == ""
لتحديد ما إذا كان العنوان موجودًا بالفعل ، ويمكننا الوصول إلى الصفحة الأولى التي تم إدخالها بهذا العنوان عن طريق الطباعةuniques[title]
.
// checkDuplicates scans the visited map for pages with duplicate titles
// and writes a report
func checkDuplicates(visited *map[string]string) {
found := false
uniques := map[string]string{}
fmt.Printf("\nChecking duplicates..\n")
for link, title := range *visited {
if uniques[title] == "" {
uniques[title] = link
} else {
found = true
fmt.Printf("Duplicate title \"%s\" in %s but already found in %s\n", title, link, uniques[title])
}
}
<span style="color:#66d9ef">if</span> !<span style="color:#a6e22e">found</span> {
<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">"No duplicates were found 😇"</span>)
}
}
الاعتمادات
لغة البرمجة Goكتاب دونوفان وكيرنيغان يستخدم زاحف الويب كمثال في جميع أنحاء الكتاب ، وتغييره في فصول مختلفة لتقديم مفاهيم جديدة. الرمز الوارد في هذه المقالة مستوحى من الكتاب.
المزيد من دروس Go:
- استخدام وكيل NGINX العكسي لخدمة خدمات Go
- عمل نسخة من هيكل في Go
- أساسيات Go Web Server
- فرز نوع الخريطة في Go
- الذهاب المؤشرات باختصار
- وأوضح Go Tags
- الذهاب تنسيق التاريخ والوقت
- معالجة JSON باستخدام Go
- وظائف متنوعة
- ورقة الغش Go Strings
- شرح واجهة Go Empty
- تصحيح الأخطاء الذهاب مع VS Code و Delve
- تقوم Named Go بإرجاع المعلمات
- توليد أرقام وسلاسل عشوائية في Go
- بنية نظام الملفات لمشروع Go
- تم تنفيذ خوارزمية البحث الثنائي في Go
- استخدام إشارات سطر الأوامر في Go
- أوضح جوباته
- أنشئ تطبيق Command Line باستخدام Go: lolcat
- إنشاء أمر CLI باستخدام Go: cowsay
- استخدام أنابيب شل مع Go
- Go CLI التعليمي: Fortune clone
- ضع قائمة بالملفات في مجلد باستخدام Go
- استخدم Go للحصول على قائمة بالمستودعات من GitHub
- اذهب ، قم بإلحاق شريحة من السلاسل بملف
- اذهب ، قم بتحويل سلسلة إلى شريحة بايت
- تصور مساهمات Git المحلية الخاصة بك مع Go
- الشروع في استخدام Go CPU وتوصيف الذاكرة
- حل الخطأ "لا يدعم الفهرسة" في برنامج Go
- قياس وقت التنفيذ في برنامج Go
- بناء زاحف الويب باستخدام Go لاكتشاف العناوين المكررة
- Go Best Practices: المؤشر أم مستقبلات القيمة؟
- Go Best Practices: هل يجب عليك استخدام طريقة أم دالة؟
- Go Data Structures: Set
- ورقة الغش في خرائط Go
- إنشاء تطبيقات لأنواع عامة في Go
- Go Data Structures: القاموس
- Go هياكل البيانات: Hash Table
- تنفيذ مستمعي الأحداث في الانتقال عبر القنوات
- Go Data Structures: Stack
- Go هياكل البيانات: قائمة الانتظار
- Go Data Structures: Binary Search Tree
- Go هياكل البيانات: رسم بياني
- Go Data Structures: قائمة مرتبطة
- الدليل الكامل إلى Go Data Structures
- مقارنة قيم Go
- هل Go موجه للكائن؟
- العمل مع قاعدة بيانات SQL في Go
- استخدام متغيرات البيئة في Go
- Go البرنامج التعليمي: REST API مدعوم من PostgreSQL
- تمكين CORS على خادم Go Web
- نشر تطبيق Go في حاوية Docker
- لماذا Go هي لغة قوية للتعلم كمطور PHP
- اذهب ، قم بإزالة io.Reader.ReadString حرف سطر جديد
- اذهب ، كيف تراقب التغييرات وتعيد بناء برنامجك
- اذهب وعد الأشهر منذ تاريخ
- الوصول إلى معلمات HTTP POST في Go