<article class="product_pod">
<div class="image_container">
<a href="catalogue/a-light-in-the-attic_1000/index.html"><img
src="media/cache/2c/da/2cdad67c44b002e7ead0cc35693c0e8b.jpg"
alt="A Light in the Attic" class="thumbnail"></a>
</div>
<p class="star-rating Three">
<i class="icon-star"></i>
<i class="icon-star"></i>
<i class="icon-star"></i>
<i class="icon-star"></i>
<i class="icon-star"></i>
</p>
<h3><a href="catalogue/a-light-in-the-attic_1000/index.html"
title="A Light in the Attic">A Light in the ...</a></h3>
<div class="product_price">
<p class="price_color">£51.77</p>
<p class="instock availability">
<i class="icon-ok"></i>
In stock
</p>
<form>
<button type="submit" class="btn btn-primary btn-block"
data-loading-text="Adding...">Add to basket</button>
</form>
</div>
</article>
!product_pod
,הקלחמ םש שי וליפא הזה טנמלאלו - ולש
.הברהב םירצק םיררוב םע יטנוולרה עדימה תא ץלחלו ,הלא לכ לע האלולב רובעל לכות ןכ
.דועו ,הז תא קוידב תושעל ידכ Scrapy לש תינבומה תפטעמה םע דובעל לוכי התא !שי ןכ
Scrapy-ה תפטעמ םע םינותנה לש המידקמ הגוצת
.שפחמ התאש םיטנמלאה תא ןוכנ םידקממ םהש חיטבהל ידכ CSS יררובו XPath ייוטיב םע י
הרעה: םינותנה תא גיצהל ךל רשפאמ תפטעמב שומישה קוידב
:רתאה לש רתאה תבותכ הירחאו shell
הדוקפב שומיש ידי לע דרגל ךנוצרבש
(venv) $ scrapy shell http://books.toscrape.com
.דעיה רתא לש HTML-ה ןכותלו Scrapy יטקייבואל השיג םע לבא ,יביטקארטניא Python RE
:שומיש תוארוה םהירחאו םינמוי המכ הארת ,וזה הדוקפה תא ליעפמ התא רשאכ
...
[s] Available Scrapy objects:
[s] scrapy scrapy module (contains scrapy.Request, scrapy.Selector, etc)
[s] crawler <scrapy.crawler.Crawler object at 0x1070dcd40>
[s] item {}
[s] request <GET https://books.toscrape.com/>
[s] response <200 https://books.toscrape.com/>
[s] settings <scrapy.settings.Settings object at 0x10727ac90>
[s] spider <BookSpider 'book' at 0x107756990>
[s] Useful shortcuts:
[s] fetch(url[, redirect=True]) Fetch URL and update local objects (by default, redirects are followed)
[s] fetch(req) Fetch a scrapy.Request and update local objects
[s] shelp() Shell help (print this help)
[s] view(response) View response in a browser
>>>
.ץלחל הצרת ילואש םינותנה תקידבל רתויב הבושחה איה response
רשאכ ,ש
:ךלש ןפדפדה לש םיחתפמה ילכ תועצמאב רבעב תישע ובש ןפואל המודב ףדה תא קודבל ךל ת
.url
ףדה לש רתאה תבותכ תא ליכמ..status
ה בצמ דוק תא ךל גיצמ-HTTP הבוגתה לש..headers
ה תורתוכ לכ תא גיצמ-HTTP הבוגתה לש..body
הבוגתה לש םיימלוגה םיתבה תא ליכמ..text
תזורחמכ הבוגתה לש חנעופמה טסקטה תא ליכמ Unicode.
:האבה הדוקפה תא ליעפהל לוכי התא ,הבוגתה לש HTTP-ה בצמ דוק תא קודבל ידכ ,המגודל
>>> response.status
200
:.text
-ב שמתשהל לוכי התא ,ףדה לש HTML-ה ןכות תא גיצהל ידכ .דרגל ך
>>> response.text
'<!DOCTYPE html>\n<!--[if lt IE 7]>
...
.םייפיצפס םיטנמלאל דקמל ידכ CSS יררובב וא XPath ייוטיבב שמתשהל רתוי ישעמ ןכלו
Scrapy תיירפסב שמתשמ Parsel ייוטיבב לפטל ידכ XPath יררובבו CSS. Parsel הקלחמ ת
>>> from parsel import Selector
>>> html = "<html><body><h1>Hello, world!</h1></body></html>"
>>> selector = Selector(text=html)
>>> text = selector.xpath("//h1/text()").get()
>>> text
'Hello, world!'
.response
טקייבואה תא חתנל ידכ Scrapy ךותב Parsel יררוב םתואב שמת
.טנרטניא חותיפ םתרקח םא רתוי ךל םירכומ ויהי הארנכ CSS יררוב לבא ,תאז םוקמב XPa
:הקתעה תועצמאב יתאצמ 'התאש ךוראה CSS-ה ררוב לש יטנוולרה קלחה קר תועצמאב רפס תר
>>> all_book_elements = response.css("article.product_pod")
>>> for book in all_book_elements:
... print(book.css("h3 > a::attr(title)").get())
...
A Light in the Attic
Tipping the Velvet
Soumission
(...)
.תזורחמכ תואצותה לש הנושארה המאתהה תא לבקמ התא ,לבקתמה Selector
-ב
.ךלש ףוסמל םירפסה תורתוכ לכ תא החלצהב סיפדהל ידכ וז השיגב שמתשהל לוכי התא
הרעה: יביכרב קמעתהל ךרוצ ןיא השעמל ,הז רתא רובע .תובורק םיתעל ליעומ HTML-ה ךותל רתוי קומע טוריפש ךכ ,דרגמ התאש רתאה לש ילאודיב
:המוד ןפואב םיריחמה תאו םירתאה תובותכ תא דקמ ,תעכ .רצוקמ CSS ררוב םע םירפסה תו
>>> for book in all_book_elements:
... print(book.css("h3 > a::attr(href)").get())
...
catalogue/a-light-in-the-attic_1000/index.html
catalogue/tipping-the-velvet_999/index.html
catalogue/soumission_998/index.html
(...)
>>> for book in all_book_elements:
... print(book.css(".price_color::text").get())
...
£51.77
£53.74
£50.10
(...)
:םינוכנה םיטנמלאה תא דקמל ידכ תשמתשה םהבש CSS ריבחתמ םיגשומה תא לוקש ,ךלש שיבכ
h3
anda
HTML ביכר לש הז גוסמ םיביכרל דקממ
>
ןב טנמלא ןייצמ
.price_color
andarticle.product_pod
הקלחמה םש תויהל ךירצ ביכר הזיאב ןייצמ ,ילנויצפוא ןפואבו הקלחמ םש ןייצמ
::text
HTML גת לש טסקטה ןכותל דקממ
::attr(href)
href
הנוכתב רתאה תבותכ הז הרקמב ,HTML תנוכת לש ךרעל דקממ
:שפחמ התאש עדימה לכל השיג ךל תתל םילוכיש רתוי םירצק CSS יררוב תרשיאו תיהיז ,הז
- Book elements
article.product_pod
- URL
h3 > a::attr(href)
- Title
h3 > a::attr(title)
- Price
.price_color::text
.ךלש שיבכעה תביתכ תעב רצק ןמז ךות םהב שמתשת התא יכ הביבסב הלאה םיררובה תא רומש
.הדירגה ךילהתב תויועט התיחפמו ןמז תכסוח וז תיביטקארטניא השיג .לעופב שיבכעה דוק
Scrapy םע ךלש Web Scraper-ה תא הנב
.ךלש שיבכעה תא תונבל ןמזה עיגה ,ךלש Scrapy-ה טקיורפ תא החלצהב תרציו םירתא קודב
.םידומע יבורמ םינותנ יכרעמ רובעל ידכ דומיעב לופיטו ,ץלחל םכנוצרבש םיטנמלאה תא
טירפב םינותנה תא ףוסא
Item
ןותייפ ןולימ םע השוע תייהש ךיאל המודב םיכרע תוצקהל לוכי התאש
טירפ
תרדגה .הרדיסו תומיא ןוגכ ,טנרטניא דוריג רובע תוישומישש תופסו
.לוהינל תנתינו הייקנ הרוצב םינותנה הנבמ תא ןייצל ךל תרשפאמ וז השיג .scra
הרעה: לש טנרטניאה תרגסמ תא ריכמ התא םא Django, לדומ תרדגהל המ
Scrapy םע חלשנ וליפא DjangoItem
םגדמ תורישי ולש תודשה תרדגה תא לב
:רפס לכ לש ריחמהו תרתוכה ,רתאה תבותכ רובע תודש םע הרצונש BooksItem
import scrapy
class BooksItem(scrapy.Item):
url = scrapy.Field()
title = scrapy.Field()
price = scrapy.Field()
.ליכי .דעיה רתאמ םיריחמו םירפס תורתוכ ,םירתא תובותכ אוצמל םכלש שיבכעל רוזעל ידכ תפטע :ךלש ןושארה שיבכעה תריציל הדוקפה תא לעפה ןכמ רחאלו , .ךלש יפיצפסה עדימה םע תעכ ךורעתש םיפסונ םימוגיפ המכ םג ללוכ הז .ךלש הרקמב < co :עדימ תוסיפ יתש דוע קפסל ךירצ אוהו םש שי יסיסב שיבכע לכל . :םינותנה תא ףוסאל ידכ .טירפ לכ רוציל ידכ הרעה: ב שומיש- Scrapy ה יבג לע היונב-Twisted framework, רתוי הבחרהל ןתינו ליעיל הדירגה ךילהת .טירפה רוניצל טירפה עפומ תא בינמ ןכמ רחאלו ךלש .םהמ דחא לכ רובע תיטמוטוא ול ארקת תרגסמה יזא , .טנרטניאהמ םינותנ איבהל ידכ ותוא חולשל לוכי התא ,הנבנש שדחה תשרב תוחונב בשייתה : Scrapy םיספדומ התיחנה ףדב רפס לכ רובע וצלוחש םינותנה תא םג תוארל רומא התא ,םינ .םיבר ינמ דחא דומע קר הז ,תאז םע !המדה םירפסה תונח לש ןושארה דומעהמ ריחמהו תרת !תרגסמב תינבומ רבכש רתוי הרישיו הקזח ךרד שי ,גאדת לא .םינימזה םיפדה לכ לע רוזח .יוארכ ךלש דעיה רתא תא קורסלו דומיע םע דדומתהל לכויש ידכ ךלש שיבכעל ד .ףסונ שומישל ותוא ןסחאלו יטנוולר עדימ ץלחל ,טנרטניא רתא לש םינוש םיפד ןיב טוונ .דומיעה ידקפ תא אוצמל ידכ רתאה תא קודבו Scrapy תפטעמ תא וא ךלש ןפדפדה תא חתפ . :דומעה תיתחתב דומיעה ירושיק תא ואצמת Books to Scrape רתאב .םינוש םירפס יטירפ גיצמו אבה דומעה תא ןעוט רתאהש הארת ,וילע ץחלתשכ .אבה דומעה :תואצותה יפד לכמ םינותנ דרגלו דומיעב לפטל ידכ ךלש םייקה BookSpider .תרזוח תורשקתהכ .הדירגה ךילהתב ךישמהל ךכבו ,תבתכש :םיפדה לכמ םינותנ דרגל ידכ בוש ותוא ליעפהל לוכי התא .דומיע םע דדומתהל תעכ רדגו Scrapy ןורחאה דומעל עיגיש דע םיאבה םידומעהמ םינותנ ץלחל ךישמתו ,דומיעה ירושיק .טנרטניא ירתא לש בחר ןווגמ םע דדומתהל םילוכיש רתוי םישימגו םיקזח םידרגמ תונבל .ךלש לנימרטב תוארל לוכי התאש (stderr) יטרדנטסה תואיגשה םרזל הלאה םינותנה תא חל .דיתעה לא הברה דובעל ךישמהלו םינותנ לש תולודג תויומכ םע דדומתהל לגוסמ תויהל רו .MongoDB ףסואב םידרוגמה םינותנה תא ןסחאל לכות דציכ דמלת ,הז ףיעסב .הצחמל םינבו .ךלש תכרעמב ותוא ןיקתהל ךילע ,MongoDB -ב שמתשהל ליחתהל לכותש ינפל הרעה: ל ףיקמה אובמב ןייעל לוכי התא ,תופסונ הרוצת תויורשפאו רת :ךלש לנימרטה תועצמאב ךלש הנקתהה תא תמאל לכות ,החלצהב MongoDB תא תנקתהש רחאל .בוש תוסנלו הנקתהה תא בוש קודבל ךרטצת ,תאז םוקמב האיגש תעדוה ךל ןתונ ךלש ףוסמה .םינותנ ןוסחאל ותוא ןיכהל ידכ הרדגה תומישמ המכ עצבל ךרטצתש ןכתיי ,MongoDB -ב ש :המָגוּדלְ .תונוכנה תואשרהה תא הל שיש אדוולו וז היירפס רוציל ךילע היהיש ןכתיי . . :םינותנ דסמ תולועפ עצבל ךל רשפאיו MongoDB תרשל ךתוא רבחי הז .ךלש לנימרטב : .ךלש שדחה :PyPI-מ .ךלש Scrapy-ה טקיורפל ךלש MongoDB עפומ לע עדימ ףיסוהל ןכומ התא ,הנקתהה תמלשה ר .וב שמתשהל לוכי התא תופסונ תורדגה וליא יבגל תורעה הברה םג ליכמ אוה .םיוסמ עדימ :ץבוקה תיתחתב MongoDB לש רוביחה יטרפ תא ףסוהו .םאתהב עדימה תא םיאתהל ךרטצת ,חראתמ MongoDB עפומל רבחתמ התא םא .ךלש ימוקמה בשח .ETL לש הדובע תמירז לש הניעטה ךילהת תא דהדהמ ,ךלש שדחה MongoDB ףסואב :טנרטניאהמ םתוא דרג ךלש שיבכעהש רחאל םיטירפ דוביעל תוטיש רפסמ הרידגמש Python ל .םידרוגמ םינותנ תומיא וא HTML ינותנ יוקינ ןוגכ ,תורחא תורטמל םג םהב שמתשהל לוכ הרעה: ה ךילהת לש טבמה תדוקנמ-ETL, תולועפל ןה שמשל לוכי טירפ ר :ףסואל םידרוגמה םיטירפה תא סינכיו MongoDB -ל רבחתי הז רוניצ .books/pipel :השוע קלח לכש המ הנה .boilerplate דוק אוה ובור ,תאז םע .דוק לש לודג שוג ומכ תו .האבה הלעפהב וב שמתשת תרגסמהש ךכ ךלש טקיורפה לש רשקהב ותוא ליעפהל ךרטצת ןיידע :ךלש שדחה רוניצה טקייבוא לש םאתומה םשה םוקימ תא ףיסוהל ןכמ רחאלו ,Python תרעה .רתוי ההובג תופידע םשוריפ רתוי םיכומנ םירפסמ .תורוניצה תא ליעפמ Scrapy ובש רדס הרעה: ב שומיש- .ךלש טנרטניאה דוריג תומישמב הקלח הרוצב הניעט תולועפ ןהו היצמרופס : .ךלש םינותנה דסמב םינותנ לופכשל ליבומש המ ,שדח ההזמ ול הצקיו שדח ךמסמכ דרגש טי .ךמסמ לכ תוהזל ידכ שמתשהל לוכי MongoDB ובש ךלש םינותנה רובע ידוחיי ההזמ הדש ןי :דדובה ףדה לש רתאה תבותכ לש hash-מ קיפתש שדח ידוחיי : 4-ו 1 תורוש יטרדנטסה היירפסה לודומ רובע דחא ,םישדח אוביי ינש 28 הרוש ל תארוק- 30-ו 29 תורוש ףסואב תותליאש תועצבמ MongoDB ותוא םע טירפ םייק 34 דע 31 תורוש יאנתה תא תוביכרמ .הלאכ םינוב םתייה םא םירחא תורוניצ ידי לע ףסונ דוביע רשפאמ הז .טירפה תא הריזחמ :ךלש טירפה תרדגהל ותוא ףסוהו :ךלש דוקב םינוש תומוקמ ינשמ .ךלש טנרטניאה דרגמ תיינב תעב ךל עיצמ Scrapy לש םינבומה םיכילהתב שומישהש תושימג .םכלש ףסואל URL תבותכ התואמ םיימעפ םתדריגש רפס םוש ףיסוי אל Python-ש ושוריפ יד .הזה טירפה תא לטבי יחכונה םושייה יזא - URL תבותכ התוא לע הרימש ךות - ונתשי הלא :Python דוקל וזה הלועפה תא םגרתמ .MongoDB םע היצקארטניא תעב תורישי ךמסמה לש :םיירקיע םיטנמוגרא ינש ריבעהל ךירצ התא זא , .ךלש ףסואב םיכמסמה תא רוציל ידכ קר וב שמתשמו ןנסמ טנמוגרא -המ הזה ךרעה תא חקול :דעוימה טקפאה תא לבקי הז דוקש ינפל ,ןמזה תמתוח יססובמ םיהזמה תא ליכמ ןיידעש ,ך .ףלא לע דימת ראשיהל ךירצ רפסמה .ךלש ףסואב ךל שי םיכמסמ המכ קודבל זאו ,םימעפ המ .םישדח םינותנב םימייק םיכרע ןכדעלו םילופכ םיטירפ לטבל לכות דציכ רתוי בוט גשומ .ןנכותמכ םילעופ םהש חיטבהלו ךלש םישיבכעב םיגאב תופנל ךל ורזעיש המצוע יבר םילכ .ןפוד תאצוי הניא תשר תדירגו ,הנכות חותיפ ךילהת לכב עירכמ קלח ןה תוקידב .הפוצמכ .הפוצמכ דבוע ךלש שיבכעהש רשאל ידכ תישיא תומאתומ תודיחי תוקידב המכ בותכת םג התא .logger-ל השיג שי רבכ םישיבכע םיטקייבואל יכ ,ותוא אבייל ךירצ אל וליפא התא .תיט :שדח ףדל טוונמ ךלש שיבכעה יתמ ירחא בוקעל ידכ םושיר ףסוהו ךלש book.pyטירפש תודשה תא רידגהל ידכ וב שמתשהל לכותש ךכ
טירפ
Scrapy לש
שיבכע
בותכ
books/
םשב ךל(venv) $ cd books/
(venv) $ scrapy genspider book https://books.toscrape.com/
Created spider 'book' using template 'basic' in module:
books.spiders.book
genspider
תועצמאב שיבכעה ימוגיפ תא תרדגה רשאכ ,שיבכעה םש תאו ,הזBooksItem
-ב שמתשהל ןכמ רחאלו ,הבוגתהמ עדימהimport scrapy
from books.items import BooksItem
class BookSpider(scrapy.Spider):
name = "book"
allowed_domains = ["books.toscrape.com"]
start_urls = ["https://books.toscrape.com/"]
def parse(self, response):
for book in response.css("article.product_pod"):
item = BooksItem()
item["url"] = book.css("h3 > a::attr(href)").get()
item["title"] = book.css("h3 > a::attr(title)").get()
item["price"] = book.css(".price_color::text").get()
yield item
האושת-ב תשמתשה ,ףוסבל .ןולימ ריבחת תועצמאב תיש
האושת
תא ךפוה .parse()BooksItem
לש םימיאstart_urls
רפסמ ללו רתאהמ םינותנ ץוליח
crawl
הדוקפה תועצמאב םשמ שיבכעה תא ליעפהל לוכי התא .ךלש Scrapy ט(venv) $ scrapy crawl book
2024-08-28 10:26:48 [scrapy.utils.log] INFO: Scrapy 2.11.2 started (bot: books)
...
{'price': '£51.77',
'title': 'A Light in the Attic',
'url': 'catalogue/a-light-in-the-attic_1000/index.html'}
2024-08-28 10:26:50 [scrapy.core.scraper] DEBUG: Scraped from <200 https://books.toscrape.com/>
{'price': '£53.74',
'title': 'Tipping the Velvet',
'url': 'catalogue/tipping-the-velvet_999/index.html'}
2024-08-28 10:26:50 [scrapy.core.scraper] DEBUG: Scraped from <200 https://books.toscrape.com/>
...
2024-08-28 10:26:50 [scrapy.core.engine] INFO: Spider closed (finished)
םירתא תובותכ רחא בוקעו ןודיעב לפט
<li class="next"><a href="catalogue/page-2.html">next</a></li>
import scrapy
from books.items import BooksItem
class BookSpider(scrapy.Spider):
name = "book"
allowed_domains = ["books.toscrape.com"]
start_urls = ["https://books.toscrape.com/"]
def parse(self, response):
for book in response.css("article.product_pod"):
item = BooksItem()
item["url"] = book.css("h3 > a::attr(href)").get()
item["title"] = book.css("h3 > a::attr(title)").get()
item["price"] = book.css(".price_color::text").get()
yield item
next_page = response.css("li.next > a::attr(href)").get()
if next_page:
next_page_url = response.urljoin(next_page)
yield scrapy.Request(url=next_page_url, callback=self.parse)
.parse()
תטישב שמתשמו תרשרש התע הזש רתאה תבותכ תא.parse()
תטיש תועצמאב בוש ,םירפ(venv) $ scrapy crawl book
MongoDB -ב םידרוגמה םינותנה תא ןסחא
ךלש בשחמב MongoDB ףסוא רדגה
$ mongod --version
db version v7.0.12
Build Info: {
"version": "7.0.12",
"gitVersion": "b6513ce0781db6818e24619e8a461eae90bc94fc",
"modules": [],
"allocator": "system",
"environment": {
"distarch": "aarch64",
"target_arch": "aarch64"
}
}
$ sudo mkdir -p /data/db
$ sudo chown -R `id -u` /data/db
27017
לדחמה תרירב תאיציל ותוא דגאיו MongoDB תרש תא ליעפת וז הלוע$ mongosh
Current Mongosh Log ID: 66868598a3dbed30a11bb1a2
Connecting to: mongodb://127.0.0.1:27017/?directConnection=true&serverSelectionTimeoutMS=2000&appName=mongosh+2.2.10
Using MongoDB: 7.0.12
Using Mongosh: 2.2.10
For mongosh info see: https://docs.mongodb.com/mongodb-shell/
test>
books
ףסואלו books_db
םינותנה דסמל ארקת התא .שדח ףסtest> use books_db
switched to db books_db
books_db> db.createCollection("books")
{ ok: 1 }
books_db> show collections
books
books_db>
םירפס ףסואל םכרד תא ואצמי םידרוגמה םינותנה לכש ךכ הז תא
Scrapy-מ MongoDB לש םינותנ דסמל רבחתה
pymongo
תא ןיקתהל ךרטצת ,תישאר .ךלש Scrapy-ה טקיורפ ךותמ(venv) $ python -m pip install pymongo
settings.py
תא חתפ# ...
MONGO_URI = "mongodb://localhost:27017"
MONGO_DATABASE = "books_db"
Scrapy רוניצ תועצמאב םינותנה תא דבע
.open_spider()
חתפנ שיבכעה רשאכ ארקנ..close_spider()
רגסנ שיבכעה רשאכ ארקנ..process_item()
גירח תולעהל וא טירפ ריזחהל וילע .טירפ רוניצ ביכר.from_crawler()
מ רוניצ רצוי-קרוס
רוניצל תונימזל תויimport pymongo
from itemadapter import ItemAdapter
class MongoPipeline:
COLLECTION_NAME = "books"
def __init__(self, mongo_uri, mongo_db):
self.mongo_uri = mongo_uri
self.mongo_db = mongo_db
@classmethod
def from_crawler(cls, crawler):
return cls(
mongo_uri=crawler.settings.get("MONGO_URI"),
mongo_db=crawler.settings.get("MONGO_DATABASE"),
)
def open_spider(self, spider):
self.client = pymongo.MongoClient(self.mongo_uri)
self.db = self.client[self.mongo_db]
def close_spider(self, spider):
self.client.close()
def process_item(self, item, spider):
self.db[self.COLLECTION_NAME].insert_one(ItemAdapter(item).asdict())
return item
itemadapter
ב תולתכ הנקתוה הליבחה .הדיחא הרוצב םהב לפטל ידכ םינושCOLLECTION_NAME
ףסוא לש םשה תא ןייצמ MongoDB ןכל םדוק תרדגהש ףסוא.__init__()
ה םע רוניצה תא לחתאמ-URI לש MongoDB מ ותוא איבמ התאש.from_crawler()
לש הבילה יביכר לכל השיג ךל הקינעמה הקלחמ תטיש איה.open_spider()
ל רוביח חתופ-MongoDB ליחתמ שיבכעה רשאכ..close_spider()
רוביח תא רגוס MongoDB םייסמ שיבכעה רשאכ..process_item()
ףסואל דרוגמ טירפ לכ ףיסומ MongoDB. רוניצ לש הבילהITEM_PIPELINES = {
"books.pipelines.MongoPipeline": 300,
}
300
םכלש תורוניצה לש תויופידעה ירד םילופכ םיכרע תפסוהמ ענמיה
1000
יכמסמ ליכמ אוהש הארת ,וגנומ תפטעמב תינושארה הצירה רחאל ךלשbooks_db> db.books.countDocuments()
1000
id
הדש ךמס לעimport hashlib
import pymongo
from itemadapter import ItemAdapter
from scrapy.exceptions import DropItem
class MongoPipeline:
COLLECTION_NAME = "books"
def __init__(self, mongo_uri, mongo_db):
self.mongo_uri = mongo_uri
self.mongo_db = mongo_db
@classmethod
def from_crawler(cls, crawler):
return cls(
mongo_uri=crawler.settings.get("MONGO_URI"),
mongo_db=crawler.settings.get("MONGO_DATABASE"),
)
def open_spider(self, spider):
self.client = pymongo.MongoClient(self.mongo_uri)
self.db = self.client[self.mongo_db]
def close_spider(self, spider):
self.client.close()
def process_item(self, item, spider):
item_id = self.compute_item_id(item)
if self.db[self.COLLECTION_NAME].find_one({"_id": item_id}):
raise DropItem(f"Duplicate item found: {item}")
else:
item["_id"] = item_id
self.db[self.COLLECTION_NAME].insert_one(ItemAdapter(item).asdict())
return item
def compute_item_id(self, item):
url = item["url"]
return hashlib.sha256(url.encode("utf-8")).hexdigest()
.compute_item_id()
השדחה רזעה תטיש תא תפסוהו .process_item.compute_item_id()
ל בוביגה טלפהelse
, תא ףיסומ הbooks/items.py
תא חתפ .BooksItimport scrapy
class BooksItem(scrapy.Item):
_id = scrapy.Field()
url = scrapy.Field()
title = scrapy.Field()
price = scrapy.Field()
BooksItem
לכ סלכאמ התא תעכו ,ךלש שיבכBooksItem
-ל .price
-ו .url
, .titl
.url
-מ ובושיח רחאל ,._id
רובע ךרעה תא ףיסומ ךלש טיר# ...
def process_item(self, item, spider):
item_id = self.compute_item_id(item)
item_dict = ItemAdapter(item).asdict()
self.db[self.COLLECTION_NAME].update_one(
filter={"_id": item_id},
update={"$set": item_dict},
upsert=True
)
return item
# ...
_id
תא רצויו טירפה םאתpymongo
םע .updabooks_db> db.books.drop()
true
books_db> db.books.countDocuments()
0
ךלש Scrapy Web Scraper-ה תא קודבו םיגאב רותיא
רגול םע ןמוי עדימ
# ...
def parse(self, response):
for book in response.css("article.product_pod"):
item = BooksItem()
item["url"] = book.css("h3 > a::attr(href)").get()
item["title"] = book.css("h3 > a::attr(title)").get()
item["price"] = book.css(".price_color::text").get()
yield item
next_page = response.css("li.next > a::attr(href)").get()
if next_page:
next_page_url = response.urljoin(next_page)
self.logger.info(
f"Navigating to next page with URL {next_page_url}."
)
yield scrapy.Request(url=next_page_url, callback=self.parse)
# ...
.INFO םושיר תמר םע שדח ףדל טוונמ שיבכעה רשאכ עדימ דעתמו ךלש BookSpider
.ךלש ףוסמל תוספדומה רתוי ההובג הרמוחו INFO לש תועדוה קר תוארל ידכ וז המרל רובע
:settings.py
-ב םיעובק ךרד תורישי ךלש שיבכעל תורבחתהה תא רידגהל לו
# ...
LOG_LEVEL = "INFO"
.הלעמו INFO לש הרמוח תגרדב םינמוי קר אלא ,םיגאב יופינ לע עדימ םוש הארת אל .תרח
.רחא ףדל טווינ Scrapy-ש עדימה תא קר לבקת ,קיר ףסוא סלכאמ התאש ןמזב שיבכעה תא ל
:MongoPipeline.process_item()
-ב רומשל תטלחה תויוליפכ תעינמ לש הקי
.שדח ףדל טוונמ Scrapy רשאכ ןמויה תועדוה תא קר הארת זא ,pymongo לש .updat
.
DropItem
גירח ספות אוה רשאכ הרהזא תיטמוטוא בתוכ Scrapy .הרהזא ינ
:ןמויה ץבוקל קר תורהזא בותכי Python-ש ךכ LOG_LEVEL
-ה תא בוש םיאתה
# ...
LOG_LEVEL = "WARNING"
LOG_FILE = "book_scraper.log"
.ךלש Scrapy טקיורפב ןמויה ץבוק םוקימ תאו םושירה תמר תא תישיא תמאתה ,וללה תורדג
:DropItem
-ב שמתשמש דוקה םע דבוע התאש החנהב - ודרוהש םיטירפה לכ לע
2024-08-28 13:11:32 [scrapy.core.scraper] WARNING: Dropped: Duplicate item found: {'price': '£51.77',
'title': 'A Light in the Attic',
'url': 'catalogue/a-light-in-the-attic_1000/index.html'}
{'price': '£51.77',
'title': 'A Light in the Attic',
'url': 'catalogue/a-light-in-the-attic_1000/index.html'}
...
!שבתשה אל רבד םושש תעדל בוט .קיר ןמוי ץבוק תוארל רומא התא זא ,תוילע םע דבוע הת
.הצירב לשכיהל םייתסי אוהש הרקמל השע ךלש שיבכעהש המ לע רוזחל ךל רשפאל ידכ ,ץבוק
errback
םע תואיגשב לפט
Scrapy רטמרפל הטיש ריבעת םא .האיגש לש תרזוח תורשקתה תייצקנופ תועצמאב ןחב תואיג
:תואיגשב לפטל ידכ ךלש שיבכעל השדח הטיש ףסוהו book.py
תא חתפ
import scrapy
from books.items import BooksItem
class BookSpider(scrapy.Spider):
# ...
def log_error(self, failure):
self.logger.error(repr(failure))
.שיבכעה עוציב תא רוצעל ילבמ תויעב ןוחבאב ךל רוזעל לוכי הז .ERROR לש הרמוח תמרב
:ךלש .log_error()
תטיש תועצמאב תואיגשב לפטי Scrapy-ש חיטבהל לוכי
# ...
def parse(self, response):
# ...
if next_page:
next_page_url = response.urljoin(next_page)
self.logger.info(
f"Navigating to next page with URL {next_page_url}."
)
yield scrapy.Request(
url=next_page_url,
callback=self.parse,
errback=self.log_error,
)
.ומשריי האיגש םיריזחמה םירפסה תונחב םיפסונ םיפדל תושקב לכש חיטבמ התא ,sc
:.start_urls
-ב לע קר ךמתסהל םוקמב .start_requests()
-
# ...
class BookSpider(scrapy.Spider):
name = "book"
allowed_domains = ["books.toscrape.com"]
start_urls = ["https://books.toscrape.com/"]
def start_requests(self):
for url in self.start_urls:
yield scrapy.Request(
url, callback=self.parse, errback=self.log_error
)
def parse(self, response):
# ...
.ךלש טנרטניאה דוריג ישיבכעב האיגשה יאנת תא תוליעיב רפשלו רותפל לכות ,err
.ןנכותמכ לעופ םכלש דרגמהש חיטבהל ידכ תוקידב בותכל ולכות דציכ לכתסת תעכ ,העיצמ
שיבכע יזוח המכ לע םותח
Scrapy הנוכמה הנוכת העיצמםיזוח, ל המודב .ךלש שיבכעה לש תוזורח
:תוטישה לש היופצה תוגהנתהה תא םירידגמ םה .ךלש שיבכעה תוטיש לש תזורחמב ביצהל לו
@url
ש רתאה תבותכ תא ןייצמ-Scrapy איבהל ךירצ.@returns
ריזחהל ךירצ שיבכעהש תושקב וא םיטירפ לש@scrapes
ליכהל םיכירצ םיטירפהש תודשה תא טרפמ.
:ךלש .parse()
תטישל הזוח ףסוהו ךלש book.py
ץבוקה תא
# ...
def parse(self, response):
"""
@url https://books.toscrape.com
@returns items 20 20
@returns request 1 50
@scrapes url title price
"""
for book in response.css("article.product_pod"):
# ...
:.parse()
לש קודה תזורחמל םיזוח העברא תפסוה
@url https://books.toscrape.com
ל רמוא-Scrapy תורחאה תוקידבהמ תחא@returns items 20 20
םומיסקמה אוה ינשהו ,םייופצה םיילמינימה םיטיר@returns request 1 50
ש ושוריפ-.parse()
תרדגהש דומיע@scrapes url title price
תודשה תא ליכהל ךירצ רזחוהש טירפ לכש ןייצ
.םדקומ בלשב ךלש דרגמה םע תויעב תוהזל ךל רוזעל תולוכיש .parse()
רו
הרעה: ףסונ הנבומ הזוח שי, @cb_kwargs
, תישיא םימא
:ךלש לנימרטב check
הדוקפב שמתשהל לוכי התא ,הזוחה תוקידב תא עצבל י
(venv) $ scrapy check book
...
----------------------------------------------------------------------
Ran 3 contracts in 1.399s
OK
.שבתשה המ תנייצמה האיגש תעדוה איצות תרגסמה ,תלשכנ תונעטהמ תחא םא .קודה תזורחמב
.םיזוח השולש הלהינ Scrapy-ש בתכנ העדוהבש הביסה וזו ,טלפב הקידב יאנתכ הז תא טרפ
.תוקידבה תא עצבלו ןגראל ידכ unittest
-ב םישמתשמ םיזוח ,עונמה הסכמל
:ןוגכ ,תונורתי המכ םיללוכ שיבכע יזוח ,רוציקב
ריהמ תומיא: הכלהכ תולעופ ךלש שיבכעה תוטישש אדוול הצור התא רשא
דועית: המ ןמז רחאל דוקב שדחמ רוקיב תעב וא תווצה ירבח רובע ליע
תויטמוטוא תוקידב: ךלש דוקה סיסבב םייוניש עצבמ התאשכ הכלהכ דוב
.םינוש םיאנתב הפוצמכ םילעופ םהש אדוולו ,ךלש טנרטניאה ידרגמ לש הקוזחתהו תונימאה
תטרופמ הקידבל הדיחי תוקידב בותכ
.םידדוב םיביכר קודבל ידכ הדיחי תוקידב בותכל בוט ןויער תויהל לוכי הז ,טוריפ רתי
הרעה: ב תודיחי תקידבל תירלופופ תורשפא-Python ישילשה דצה תיירפ
.הפוצמכ תוגהנתמ ךלש שיבכעה תוטישש חיטבהל ידכ םיגלגלמ תושקבו תובוגתב ךורכ הז .S
:םיצבק ינש רוצ ,הייקית התוא ךותב .tests/
תייקית רוצ ,הנוילעה המרב
__init__.py
תליבחכ היקיתה תא לחתאמ Python.test_book.py
ךלש דרגמה רובע הדיחיה תוקידב תא םייקי.
.תיפיצפס תפסות םע הז לע תונבל זאו ,ךלש שיבכעה יזוח םיסכמ רבכש המ תא שדחמ לוקשל
.הזה רסחה תא אלמל ידכ הדיחי ןחבמ בותכל בוט ןויער תויהל לוכי הז .ץלחמ אוהש םיטנ
:דומיעל רתאה תבותכ רובע השדח השקב תרצויו םירפסה לכ תא תרזחאמ ,HTML-המ םינוכנה
import unittest
class BookSpiderTest(unittest.TestCase):
def test_parse_scrapes_all_items(self):
"""Test if the spider scrapes books and pagination links."""
pass
def test_parse_scrapes_correct_book_information(self):
"""Test if the spider scrapes the correct information for each book."""
pass
def test_parse_creates_pagination_request(self):
"""Test if the spider creates a pagination request correctly."""
pass
if __name__ == "__main__":
unittest.main()
.יוארכ דקפתל ידכ הכירצ הקידב לכש השורדה הרדגהה תא תרצויש .setUp()
:.setUp()
תטישל רובעל בוט דמעומ הז ,ןחבמ לכ רובע וז הרדגהל קקדזתש
import unittest
from scrapy.http import HtmlResponse
from books.spiders.book import BookSpider
class BookSpiderTest(unittest.TestCase):
def setUp(self):
self.spider = BookSpider()
self.example_html = """
Insert the example HTML here
"""
self.response = HtmlResponse(
url="https://books.toscrape.com",
body=self.example_html,
encoding="utf-8"
)
# ...
.ךלש דוקה סיסב תא עירכהל ילבמ ,יתימאה ףדה לש HTML-ל רשפאה לככ בורק היהי המגודל
:books/tests/sample.html
-ב הדרוהל םינתינה םירמוחהמ הזה ךירדמה שמת
.self.example_html
-ל ותוא תוצקהל ידכ תולופכ-תושלושמה תואכרמה ןיב
הרעה: ב שמתשהל ךנוצרב םא-HTML ןפדפדה תועצמאב ותוא רזחאל םג לכ
.טנרטניאל רבוחמ התא םא קר דובעיו םיעוציב שנוע הווהמ אוה ,ץר ךלש הקידבה טפירקסש
:לשכיהל ךלש ןושארה ןחבמל םורגל לוכי התא ,םוקמב ךלש הרדגהה םע
# ...
class BookSpiderTest(unittest.TestCase):
# ...
def test_parse_scrapes_all_items(self):
"""Test if the spider scrapes all books and pagination links."""
# There should be two book items and one pagination request
book_items = []
pagination_requests = []
self.assertEqual(len(book_items), 2)
self.assertEqual(len(pagination_requests), 1)
:unittest
הדוקפה תא לעפה ,םש רבכ ךניא םא ,ךלש הנוילעה המרב
(venv) $ python -m unittest
.F.
======================================================================
FAIL: test_parse_scrapes_all_items (tests.test_book.BookSpiderTest.test_parse_scrapes_all_items)
Test if the spider scrapes all items including books and pagination links.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/path/to/books/tests/test_book.py", line 169, in test_parse_scrapes_all_items
self.assertEqual(len(book_items), 2)
AssertionError: 0 != 2
----------------------------------------------------------------------
Ran 3 tests in 0.011s
FAILED (failures=1)
:רובעי ןחבמהש ידכ םירסחה םיקלחה תא ופיסוהו דוקל הרזח וצפק .הלש רזחהה ךרעב לפטמ
# ...
from scrapy.http import HtmlResponse, Request
from books.items import BooksItem
class BookSpiderTest(unittest.TestCase):
# ...
def test_parse_scrapes_all_items(self):
"""Test if the spider scrapes all books and pagination links."""
# Collect the items produced by the generator in a list
# so that it's possible to iterate over it more than once.
results = list(self.spider.parse(self.response))
# There should be two book items and one pagination request
book_items = [item for item in results if isinstance(item, BooksItem)]
pagination_requests = [
item for item in results if isinstance(item, Request)
]
self.assertEqual(len(book_items), 2)
self.assertEqual(len(pagination_requests), 1)
.books.items
-מ הז תא אביימ התא זא ,BooksItem
-ב רפסה
.ךלש תונעטה לומ קדוב התאש המ הז זא ,דומיע לש תחא רתא תבותכו רפס יטירפ ינש ליכמ
הרעה: ב תשמתשה םא-HTML רובעי ןחבמהש ידכ םאתהב ךלש העיבקה תא ם
:רובעל תורומא תוקידבה תשולש לכו ,תרחא םעפב unittest
תא ץירהל לכות
(venv) $ python -m unittest
...
----------------------------------------------------------------------
Ran 3 tests in 0.011s
:םושייל המגוד אוצמל לכות ,הטמל לפקתמה קלחב .המוד השיגב תורחאה הקידבה תוטיש יתש
:ךלש test_book.py
ץבוקב ותוא קיבדהלו הטמל דוקה תא קיתעהל לוכי התא
import unittest
from scrapy.http import HtmlResponse, Request
from books.items import BooksItem
from books.spiders.book import BookSpider
class BookSpiderTest(unittest.TestCase):
def setUp(self):
self.spider = BookSpider()
self.example_html = """
Insert the example HTML here
"""
self.response = HtmlResponse(
url="https://books.toscrape.com",
body=self.example_html,
encoding="utf-8",
)
def test_parse_scrapes_all_items(self):
"""Test if the spider scrapes all books and pagination links."""
# Collect the items produced by the generator in a list
# so that it's possible to iterate over it more than once.
results = list(self.spider.parse(self.response))
# There should be two book items and one pagination request
book_items = [
item for item in results if isinstance(item, BooksItem)
]
pagination_requests = [
item for item in results if isinstance(item, Request)
]
self.assertEqual(len(book_items), 2)
self.assertEqual(len(pagination_requests), 1)
def test_parse_scrapes_correct_book_information(self):
"""Test if the spider scrapes the correct information for each book."""
results_generator = self.spider.parse(self.response)
# Book 1
book_1 = next(results_generator)
self.assertEqual(
book_1["url"], "catalogue/a-light-in-the-attic_1000/index.html"
)
self.assertEqual(book_1["title"], "A Light in the Attic")
self.assertEqual(book_1["price"], "£51.77")
# Book 2
book_2 = next(results_generator)
self.assertEqual(
book_2["url"], "catalogue/tipping-the-velvet_999/index.html"
)
self.assertEqual(book_2["title"], "Tipping the Velvet")
self.assertEqual(book_2["price"], "£53.74")
def test_parse_creates_pagination_request(self):
"""Test if the spider creates a pagination request correctly."""
results = list(self.spider.parse(self.response))
next_page_request = results[-1]
self.assertIsInstance(next_page_request, Request)
self.assertEqual(
next_page_request.url,
"https://books.toscrape.com/catalogue/page-2.html",
)
if __name__ == "__main__":
unittest.main()
:הדרוהל םיבאשמב books/tests/sample.html
-ב אוצמל לכותש המגודל HTML
.חותיפה ךילהתב םדקומ בלשב תויעב ןקתלו תוהזל תרזועו ,תונוכנ רותיאל םיקדבנ .ןימאו קזח טנרטניא דרגמ תיינבל ינויח וללה םילושכמה םע דדומתהל םינכומ תויהל ,הד .ולש תבחרנה תיגולוקאה תכרעמהו Scrapy תועצמאב םהילע רבגתהל םיישעמ תונורתפ המכ ר .רתוי םירגתאמ םירתאמ םג ךרע ירקי םינותנ ץלחל םילגוסמה םידימע םידרגמ תונבל רתוי .לשכיהל ךלש שיבכעל ומרגי אל תוינמז תויעבש אדוול תרזוע ךלש טקיורפב רזוח ןויסינ Scrapy תואיגש לש םימיוסמ םיגוסב ולקתנש תושקב לש רזוח ןויסינל תינבומ הכימת תקפס :ךלש טקיורפה רובע םיאתהל הצרת ילואש שולש שי ,תונימזה הרוצתה תויורשפא ןיבמ .ךלש : .דרגמה יטקיורפ בור רובע בוט ןויער ללכ ךרדב איה תמשיימ Scrapy-ש לדחמה תרירב תור .תופלוח תויעב לש העפשהה תא תיחפהלו רתוי ןימא םינותנ ץוליח גישהל לוכי התא ,הז ל :םהב ןיינועמ התאש םינותנל השיג לבקל ןיידע ידכ טוקנל לוכי התאש תושיג המכ שי .ימ תושקב לפכש: תואירק תועצמאב ךל םישורדה םינותנה תא באוש ךלש ןפד ב שמתשה-Splash לש שארמ דוביעל JavaScript: Splash דבעל ךל רשפא יטמוטואל ןפדפד ךופה: ומכ םילכ Selenium ו-Playwright עוציב ללו .הדובעה תא עצבל ךל ורזעיש תוליעומ תויצרגטניא ךל תקפסמ Scrapy ,ךלש טנרטניאה דור .הנתשמ ץמאמ תדימב וללה םיעצמאהמ קלח ףוקעל לוכי התא .םהלש םיבאשמב ןגוה שומיש חי Scrapy ץבוקב תורישי רתויב תוצופנה תושיגהמ המכ תישיא םיאתהל ךל רשפאמ sett :יתימא ןפדפד תוקחל רוזעל הלוכי ךלש טקיורפה תורדגהב תישיא םאתומ שמתשמ ןכוס תרדג :המיסחל יוכיסה תא תיחפהלו תושקב ץיפהל רוזעל לוכיש המ ,יסקורפו םישמתשמ ינכוס בב :טוב-יטנא יעצמא תלעפהמ ענמיהל רוזעל םג לוכיש המ ,תושקב ןיב םיבוכיע סינכהל לוכי :לדחמ תרירבכ תלעפומה ,ךכל הרדגה םג שי Scrapy-ל .טנרטניא יקרוס רובע רתאה לש תוא .היצקארטניא םתיא םייקמ התאש םירתאה תא םידבכמו םיליעי ,םיקזח ךלש םידרגמהש חיטבה !הפי הדובע .ETL-ה ךילהת רחא בקועש םלש טנרטניא דוריג טקיורפל MongoDB-בו Scrapy- .הדירג-יטנא ינונגנמ תפיקעו ימניד ןכות םע תודדומתה ,םירזוח תונויסינ לוהינ ןוגכ דציכ תדמל ,הז ךירדמב: !דבכמ ראשיהו דרגל ךישמת .בטיה ךתוא ותרשי הזה ךירדמהמ תרבצש םילכהו עדיה ,טנרטני םיצופנ טנרטניא דוריג ירגתא םע דדומתה
ולשכנש תושקב בוש הסנ
RETRY_ENABLED
: לדחמ תרירבכ לעפומ הז .רזוח ןויסינ לש עצמאה תנכות תRETRY_TIMES
: םיימעפ הלשכנש השקב בוש הסנמ תרגסמה ,לדחמ תרירבכ .הלשRETRY_HTTP_CODES
: תבוגת ידוק תא רידגמ HTTP רזוח ןויסינ ליעפהל םירsettings.py
ץבוקב הלאה םיכרעה תא תונשל לוכי התא זא ,לדחמה תרירבל# ...
RETRY_TIMES = 3
RETRY_HTTP_CODES = [500, 429]
ימניד ןכות םע דדומתה
הדירג דגנ םינונגנמ להנ
# ...
USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3"
# ...
DOWNLOADER_MIDDLEWARES = {
"scrapy.downloadermiddlewares.useragent.UserAgentMiddleware": None,
"scrapy_user_agents.middlewares.RandomUserAgentMiddleware": 400,
}
# ...
# ...
DOWNLOAD_DELAY = 2
# ...
ROBOTSTXT_OBEY = True
# ...
הנָקָסְמַ