Mengikis Web Dengan Scrapy dan MongoDB
Scrapy ialah rangka kerja mengikis web Python yang teguh yang boleh mengurus permintaan secara tidak segerak, mengikuti pautan dan menghuraikan kandungan tapak. Untuk menyimpan data yang dikikis, anda boleh menggunakan MongoDB, pangkalan data NoSQL berskala, yang menyimpan data dalam format seperti JSON. Menggabungkan Scrapy dengan MongoDB menawarkan penyelesaian yang berkuasa untuk projek mengikis web, memanfaatkan kecekapan Scrapy dan storan data fleksibel MongoDB.
Dalam tutorial ini, anda akan belajar cara untuk:
- Sediakan dan konfigurasikan Projek Scrapy
- Bina pengikis web berfungsi dengan Scrapy
- Ekstrak data daripada tapak web menggunakan pemilih
- Simpan data yang dikikis dalam pangkalan data MongoDB
- Uji dan nyahpepijat pengikis web Scrapy anda
Jika anda baharu dalam pengikisan web dan anda sedang mencari alatan yang fleksibel dan berskala, maka ini ialah tutorial yang sesuai untuk anda. Anda juga akan mendapat manfaat daripada mempelajari kit alat ini jika anda pernah mengikis tapak sebelum ini, tetapi kerumitan projek anda telah diatasi menggunakan Sup dan Permintaan Cantik.
Untuk memanfaatkan sepenuhnya tutorial ini, anda harus mempunyai pengetahuan pengaturcaraan Python asas, memahami pengaturcaraan berorientasikan objek, bekerja dengan selesa dengan pakej pihak ketiga dan biasa dengan HTML dan CSS.
Pada akhirnya, anda akan tahu cara mendapatkan, menghuraikan dan menyimpan data statik daripada Internet, dan anda akan terbiasa dengan beberapa alatan berguna yang membolehkan anda pergi lebih mendalam.
Sediakan Perancah Pengikis
Anda akan bermula dengan menyediakan alatan yang diperlukan dan mencipta struktur projek asas yang akan berfungsi sebagai tulang belakang untuk tugas mengikis anda.
Semasa menjalankan tutorial, anda akan membina projek mengikis web yang lengkap, mendekatinya sebagai proses ETL (Ekstrak, Transformasi, Muatkan):
- Ekstrak data daripada tapak web menggunakan labah-labah Scrapy sebagai perangkak web anda.
- Ubah data ini, contohnya dengan membersihkan atau mengesahkannya, menggunakan saluran paip item.
- Muatkan data yang diubah ke dalam sistem storan seperti MongoDB dengan saluran paip item.
Scrapy menyediakan perancah untuk semua proses ini dan anda akan memanfaatkan perancah itu untuk mempelajari pengikisan web mengikut struktur teguh yang Scrapy sediakan dan banyak projek pengikisan web skala perusahaan bergantung kepada.
Nota: Dalam projek mengikis web Scrapy, labah-labah ialah kelas Python yang mentakrifkan cara merangkak tapak web tertentu atau sekumpulan tapak web. Ia mengandungi logik untuk membuat permintaan, menghuraikan respons dan mengekstrak data yang dikehendaki.
Mula-mula, anda akan memasang Scrapy dan mencipta projek Scrapy baharu, kemudian meneroka struktur projek yang dijana secara automatik untuk memastikan anda dilengkapi dengan baik untuk meneruskan pembinaan pengikis web yang berprestasi.
Pasang Pakej Scrapy
Untuk bermula dengan Scrapy, anda perlu memasangnya terlebih dahulu menggunakan pip
. Cipta dan aktifkan persekitaran maya untuk memastikan pemasangan berasingan daripada pemasangan Python global anda. Kemudian, anda boleh memasang Scrapy:
(venv) $ python -m pip install scrapy
Selepas pemasangan selesai, anda boleh mengesahkannya dengan menjalankan perintah scrapy
dan melihat output:
(venv) $ scrapy
Scrapy 2.11.2 - no active project
Usage:
scrapy <command> [options] [args]
Available commands:
bench Run quick benchmark test
fetch Fetch a URL using the Scrapy downloader
genspider Generate new spider using pre-defined templates
runspider Run a self-contained spider (without creating a project)
settings Get settings values
shell Interactive scraping console
startproject Create new project
version Print Scrapy version
view Open URL in browser, as seen by Scrapy
[ more ] More commands available when run from project directory
Use "scrapy <command> -h" to see more info about a command
Program baris arahan (CLI) harus memaparkan teks bantuan Scrapy. Ini mengesahkan bahawa anda memasang pakej dengan betul. Anda seterusnya akan menjalankan perintah startproject
yang diserlahkan untuk membuat projek.
Buat Projek Scrapy
Scrapy dibina di sekitar projek. Secara amnya, anda akan membuat projek baharu untuk setiap projek mengikis web yang sedang anda usahakan. Dalam tutorial ini, anda akan berusaha mengikis tapak web yang dipanggil Buku untuk Mengikis, jadi anda boleh memanggil projek anda buku.
Seperti yang anda mungkin telah kenal pasti dalam teks bantuan, rangka kerja menyediakan arahan untuk mencipta projek baharu:
(venv) $ scrapy startproject books
Perintah ini menyerupai perintah yang anda gunakan untuk menjana perancah untuk projek Django baharu, dan Scrapy dan Django ialah dua rangka kerja matang yang boleh berinteraksi dengan baik antara satu sama lain.
Dalam Scrapy, perintah startproject
menjana projek Scrapy baharu bernama books yang menyediakan anda dengan struktur projek yang mengandungi beberapa fail dan direktori yang dijana secara automatik:
books/
│
├── books/
│ │
│ ├── spiders/
│ │ └── __init__.py
│ │
│ ├── __init__.py
│ ├── items.py
│ ├── middlewares.py
│ ├── pipelines.py
│ └── settings.py
│
└── scrapy.cfg
Scrapy bergantung pada struktur ini dan ia akan membantu anda dalam mengatur projek Scrapy anda dengan cekap. Klik di sekeliling fail dan folder yang berbeza dan fikirkan tentang maksud setiap satu daripadanya dalam konteks projek mengikis web:
buku/
ialah direktori akar projek Scrapy anda.buku/labah-labah/
ialah direktori untuk menyimpan definisi labah-labah anda.books/items.py
ialah fail untuk mentakrifkan struktur data item anda yang dikikis.books/middlewares.py
ialah fail untuk mengurus middleware tersuai anda.books/pipelines.py
ialah fail untuk menentukan saluran paip pemprosesan item.books/settings.py
ialah fail tetapan untuk mengkonfigurasi projek anda.
Dalam tutorial ini, anda akan menyentuh kebanyakan fail yang anda boleh lihat disenaraikan di atas dan menambah fungsi tersuai anda padanya. Dengan penyediaan perancah projek anda, anda kini akan melihat tapak web sumber yang ingin anda ekstrak maklumat.
Periksa Sumber Yang Anda Ingin Kikis
Anda tidak akan mencapai cita-cita mengikis web anda jika anda tidak mengetahui struktur tapak web yang ingin anda carik maklumat. Oleh itu, langkah awal yang penting dalam proses ini untuk memeriksa tapak web sasaran. Cara terpantas untuk memeriksa tapak web adalah dengan membuka penyemak imbas anda, menavigasi ke tapak dan klik sekeliling.
Lihat Laman Web dalam Penyemak Imbas Anda
Mulakan dengan menavigasi tapak web sasaran dalam penyemak imbas anda sebagai pengguna. Kenal pasti unsur-unsur khusus yang ingin anda kikis. Contohnya, jika anda mengikis tajuk buku, harga dan URL dari kedai buku, kemudian periksa HTML untuk mencari elemen ini. Anda boleh melakukan ini dengan sebaiknya menggunakan alat pembangun terbina dalam penyemak imbas anda.
Nota: Setiap penyemak imbas mempunyai cara yang berbeza sedikit untuk membuka alatan pembangun. Jika anda tidak pernah melihat alat pembangun penyemak imbas sebelum ini, maka anda mungkin perlu melakukan carian web tentang cara membukanya dalam kes khusus anda. Anda juga boleh menyemak panduan tentang cara memeriksa tapak menggunakan alat pembangun.
Setelah anda membuka alatan pembangun dalam penyemak imbas anda dan mengenal pasti item buku, anda akan dapat melihat antara muka yang kelihatan serupa dengan yang ditunjukkan di bawah:
Selepas anda mendapat idea yang baik tentang cara maklumat disusun di tapak, anda perlu mengumpulkan pemilih unik untuk setiap maklumat yang ingin anda carik. Anda akan mahu mengekstrak tiga titik data daripada setiap buku:
- Tajuk
- harga
- URL
Anda boleh menggunakan alat pembangun untuk mengumpulkan ungkapan XPath atau pemilih CSS untuk setiap elemen ini dengan mengklik kanan pada elemen dan memilih Salin → Salin pemilih atau Salin → Salin XPath . Contohnya, jika anda menyalin pemilih CSS untuk buku pertama di tapak, maka anda harus mendapatkan nilai berikut untuk setiap elemen:
- Title
#default > div > div > div > div > section > div:nth-child(2) > ol > li:nth-child(1) > artikel > h3 > a
- Price
#default > div > div > div > div > section > div:nth-child(2) > ol > li:nth-child(1) > artikel > div.product_price > p.price_color
- URL
#default > div > div > div > div > section > div:nth-child(2) > ol > li:nth-child(1) > artikel > h3 > a
Ini terlalu panjang untuk dijejaki! Walau bagaimanapun, anda mungkin perasan bahawa semuanya bermula dengan cara yang sama. Itu kerana ia adalah laluan mutlak yang bermula pada elemen akar dokumen HTML.
Nota: Menggunakan pemilih mutlak boleh menjadi cara cepat untuk mengenal pasti elemen dalam dokumen, tetapi ia rapuh dan terdedah kepada pecah jika terdapat sebarang perubahan dalam struktur dokumen. Untuk kod yang lebih teguh dan boleh diselenggara, pemilih relatif biasanya merupakan pilihan yang lebih baik.
Nampaknya anda boleh menemui semua maklumat yang anda minati dalam teg <article>
. Kembali ke penyemak imbas anda dan kenal pasti elemen <article>
itu:
<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>
Baris yang diserlahkan mengesahkan bahawa anda boleh menemui ketiga-tiga maklumat untuk setiap buku dalam elemen <article>
nya—dan elemen itu juga mempunyai nama kelas, product_pod
!
Oleh itu, anda boleh merancang untuk menyasarkan semua elemen <article>
dahulu dengan nama kelas product_pod
. Ini mewakili elemen induk satu buku, yang mengandungi semua maklumat yang anda inginkan. Anda kemudiannya boleh mengulangi semua ini dan mengekstrak maklumat yang berkaitan dengan pemilih yang lebih pendek.
Tetapi adakah terdapat cara yang baik untuk mengesahkan pemilih yang akan melakukan pembidaan anda, sebelum menulis keseluruhan skrip labah-labah? Ya ada! Anda boleh bekerja dengan cangkerang terbina dalam Scrapy untuk melakukan perkara itu, dan banyak lagi.
Pratonton Data Dengan Cangkang Scrapy
Sebelum menulis labah-labah yang lengkap, selalunya berguna untuk melihat dan menguji cara anda mengekstrak data daripada halaman web. Cangkang Scrapy ialah alat interaktif yang membolehkan anda memeriksa dan mengekstrak data daripada halaman web dalam masa nyata. Ini membantu dalam bereksperimen dengan ekspresi XPath dan pemilih CSS untuk memastikan ia menyasarkan elemen yang anda cari dengan betul.
Nota: Menggunakan cangkerang membolehkan anda melihat data tepat seperti ia kembali daripada permintaan HTTP. Apabila anda memeriksa halaman yang diberikan dalam penyemak imbas anda, anda melihat perwakilan dalaman penyemak imbas Model Objek Dokumen (DOM). Untuk menciptanya, penyemak imbas anda mungkin telah melaksanakan kod JavaScript dan membersihkan HTML yang salah, yang menjadikan HTML yang terhasil yang anda boleh periksa menggunakan alat pembangun berbeza daripada HTML asal.
Anda boleh membuka cangkerang dan menghalakannya ke tapak yang ingin dikikis dengan menggunakan perintah shell
diikuti dengan URL tapak:
(venv) $ scrapy shell http://books.toscrape.com
Perintah ini memuatkan URL yang ditentukan dan memberi anda persekitaran interaktif untuk meneroka struktur HTML halaman. Anda boleh menganggapnya seperti REPL Python interaktif, tetapi dengan akses kepada objek Scrapy dan kandungan HTML tapak sasaran.
Apabila anda menjalankan arahan ini, anda akan melihat beberapa log diikuti dengan arahan penggunaan:
...
[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
>>>
Cangkerang menyediakan beberapa objek pra-import, dengan respon
menjadi yang paling penting untuk memeriksa data yang anda mungkin ingin ekstrak.
Objek respons
mempunyai beberapa atribut dan kaedah berguna yang membolehkan anda memeriksa halaman sama seperti cara anda menggunakan alat pembangun penyemak imbas anda sebelum ini:
.url
mengandungi URL halaman..status
menunjukkan kepada anda kod status HTTP respons..headers
memaparkan semua pengepala HTTP respons..body
mengandungi bait mentah respons..text
mengandungi teks jawapan yang dinyahkodkan sebagai rentetan Unicode.
Sebagai contoh, untuk menyemak kod status HTTP respons, anda boleh menjalankan arahan berikut:
>>> response.status
200
Objek respons
juga mengandungi kandungan halaman yang telah anda muatkan. Anda boleh menggunakannya untuk memeriksa HTML dan mencari elemen yang ingin anda kikis. Untuk melihat kandungan HTML halaman, anda boleh menggunakan .text
:
>>> response.text
'<!DOCTYPE html>\n<!--[if lt IE 7]>
...
Walau bagaimanapun, mencetak keseluruhan HTML biasanya sangat menggembirakan, jadi lebih praktikal untuk menggunakan ungkapan XPath atau pemilih CSS untuk menyasarkan elemen tertentu.
Scrapy menggunakan perpustakaan Parsel untuk mengendalikan ekspresi XPath dan pemilih CSS. Parsel menyediakan kelas Selector
yang boleh anda gunakan secara bebas daripada Scrapy. Ini berguna jika anda ingin bereksperimen dengan penghuraian HTML:
>>> 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!'
Anda boleh menggunakan pemilih Parsel yang sama dalam Scrapy untuk menghuraikan objek respons
.
Anda kini boleh mencuba pemilih CSS yang anda temui menggunakan alat pembangun anda untuk mengesahkan sama ada mereka boleh menyasarkan maklumat yang anda inginkan. Anda juga boleh menggunakan ungkapan XPath sebaliknya, tetapi pemilih CSS mungkin akan lebih biasa kepada anda jika anda telah meneroka pembangunan web.
Mula-mula, anda ingin menelusuri HTML ke elemen <article>
yang mengandungi semua maklumat untuk satu buku, kemudian pilih setiap tajuk buku menggunakan hanya bahagian yang berkaitan dalam pemilih CSS panjang yang anda' telah ditemui melalui penyalinan:
>>> 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
(...)
Anda bermula dengan menyasarkan semua elemen bekas buku dan menetapkan hasilnya kepada semua_elemen_buku
. Panggilan ke .css()
mengembalikan objek Selector
yang lain, supaya anda boleh terus menelusuri lebih lanjut menggunakan panggilan lain ke .css()
. Kali ini, anda menentukan bahawa anda mahukan nilai atribut HTML title
semua elemen pautan yang terkandung dalam elemen <h3>
. Dengan memanggil .get()
pada Selector
yang terhasil, anda mendapat padanan pertama hasil sebagai rentetan.
Anda boleh menggunakan pendekatan ini untuk berjaya mencetak semua tajuk buku ke terminal anda.
Nota: Untuk tapak ini, sebenarnya tidak perlu menelusuri elemen <article>
dahulu, kerana halaman itu tidak mengandungi sebarang <h3> yang lain
elemen yang mengandungi pautan. Oleh itu, anda boleh menjalankan pertanyaan .css()
yang sama terus pada respons
untuk mendapatkan hasil yang sama.
Walau bagaimanapun, secara amnya anda tidak boleh bergantung pada sesuatu seperti itu. Semuanya bergantung pada struktur individu tapak yang anda sedang mengikis, jadi penggerudian lebih dalam ke dalam HTML selalunya membantu.
Anda mengesahkan bahawa anda boleh mendapatkan semua tajuk buku dengan pemilih CSS yang dipendekkan. Sekarang, sasarkan URL dan harga dengan cara yang sama:
>>> 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
(...)
Hebat, anda boleh menangani semua maklumat yang anda inginkan. Sebelum menggunakan pendekatan ini dalam labah-labah anda, pertimbangkan konsep daripada sintaks CSS yang telah anda gunakan untuk menyasarkan elemen yang betul:
h3
anda
Menyasarkan elemen jenis elemen HTML tersebut
>
Menunjukkan elemen kanak-kanak
.price_color
andarticle.product_pod
Menunjukkan nama kelas dan, secara pilihan, menentukan elemen mana nama kelas sepatutnya
::text
Menyasarkan kandungan teks tag HTML
::attr(href)
Menyasarkan nilai atribut HTML, dalam kes ini URL dalam atribut
href
Anda boleh menggunakan sintaks CSS dalam pemilih untuk menyasarkan maklumat khusus pada tapak web, sama ada teks dalam elemen, atau bahkan nilai atribut HTML. Pada ketika ini, anda telah mengenal pasti dan mengesahkan pemilih CSS yang lebih pendek yang boleh memberi anda akses kepada semua maklumat yang anda cari:
- Book elements
article.product_pod
- URL
h3 > a::attr(href)
- Title
h3 > a::attr(title)
- Price
.price_color::text
Itu kelihatan lebih mudah diurus! Anda boleh menyimpan pemilih ini kerana anda akan menggunakannya dalam masa yang singkat semasa menulis labah-labah anda.
Menggunakan cangkerang Scrapy dengan cara ini membantu anda memperhalusi pemilih anda dan memastikan bahawa labah-labah anda akan mengekstrak data yang dikehendaki dengan betul apabila anda menulis kod labah-labah sebenar. Pendekatan interaktif ini menjimatkan masa dan mengurangkan ralat dalam proses mengikis.
Bina Pengikis Web Anda Dengan Scrapy
Memandangkan anda mempunyai pemahaman yang kukuh tentang cara memeriksa tapak web dan telah berjaya mencipta projek Scrapy anda, tiba masanya untuk membina labah-labah anda. Tetapi menulis labah-labah anda melibatkan lebih daripada sekadar menghalakannya ke tapak web dan melepaskannya. Anda perlu menganyam sarang labah-labah dengan teliti untuk memastikan ia mengumpul data yang anda perlukan dengan tepat dan cekap.
Dalam bahagian ini, anda akan merangkumi konsep penting seperti membuat kelas item untuk menstruktur data anda, menggunakan pemilih untuk menentukan elemen yang anda ingin ekstrak dan mengendalikan penomboran untuk merentasi set data berbilang halaman. Pada penghujung bahagian ini, anda bukan sahaja akan mempunyai pengikis web yang berfungsi, tetapi anda juga akan mempunyai pemahaman yang lebih mendalam tentang cara menyesuaikan dan mengoptimumkan labah-labah anda untuk menangani pelbagai cabaran mengikis web.
Kumpulkan Data dalam Item
Item
ialah bekas yang mentakrifkan struktur data yang ingin anda kumpulkan. Ia adalah pemetaan Python, yang bermaksud bahawa anda boleh menetapkan nilai sama seperti yang anda lakukan dengan kamus Python.
Item
juga menyediakan ciri tambahan yang berguna untuk mengikis web, seperti pengesahan dan penyirian. Menentukan Item
untuk data anda membantu menyusun dan menyeragamkan data yang anda kikis, yang bermakna kurang usaha untuk memproses dan menyimpannya kemudian.
Untuk mula menggunakan item, anda perlu mencipta kelas yang mewarisi daripada scrapy.Item
dan mentakrifkan medan yang anda ingin mengikis sebagai contoh scrapy.Field
. Pendekatan ini membolehkan anda menentukan struktur data dengan cara yang bersih dan terurus.
Nota: Jika anda biasa dengan rangka kerja web Django, maka anda mungkin mengenali cara mentakrif item ini. Ia menggunakan sintaks definisi kelas yang dipermudahkan seperti mentakrifkan model Django.
Scrapy juga dihantar dengan DjangoItem
khusus yang mendapat definisi medannya terus daripada model Django.
Struktur projek lalai yang anda buat sebelum ini menyediakan tempat untuk menentukan kelas item anda dalam fail yang dipanggil items.py
. Buka fail dan tambah kod pada kelas BooksItem
yang dijana dengan medan untuk URL, tajuk dan harga setiap buku:
import scrapy
class BooksItem(scrapy.Item):
url = scrapy.Field()
title = scrapy.Field()
price = scrapy.Field()
Dalam contoh ini, BooksItem
mewarisi daripada Item
dan anda mentakrifkan .url
, .title
dan .price
sebagai medan. Kelas Field
ialah pemegang tempat yang hanya alias kepada dikt
Python. Ia disepadukan dengan Item
supaya anda boleh menggunakannya untuk mentakrifkan medan yang Item
akan mengandungi.
Memandangkan anda telah menentukan BooksItem
, anda boleh menggunakannya dalam labah-labah anda untuk mengumpul data. Anda akan menggunakan pemilih yang anda terokai menggunakan cangkerang untuk membantu labah-labah anda mencari URL, tajuk buku dan harga daripada tapak web sasaran.
Tulis Labah-labah
Scrapy
Akhirnya tiba masanya untuk memasang kepingan yang telah anda bina dengan teliti sehingga kini. Rangka kerja mempunyai arahan lain yang boleh anda gunakan untuk membuat labah-labah dengan mudah dalam projek anda. Pastikan anda telah menavigasi ke dalam folder projek anda yang dipanggil books/
, dan kemudian jalankan arahan untuk menjana labah-labah pertama anda:
(venv) $ cd books/
(venv) $ scrapy genspider book https://books.toscrape.com/
Created spider 'book' using template 'basic' in module:
books.spiders.book
Dengan menghantar nama labah-labah dan URL sasaran kepada genspider
, Scrapy mencipta fail baharu dalam direktori spiders/
projek anda. Buka dan lihat. Anda akan melihat kelas baharu yang mewarisi daripada scrapy.Spider
dan mempunyai nama yang anda masukkan, dengan Spider
sebagai akhiran—contohnya, BookSpider
dalam kes anda. Ia juga termasuk beberapa perancah tambahan yang kini anda akan edit dengan maklumat khusus anda.
Mana-mana labah-labah asas mempunyai nama dan perlu memberikan dua lagi maklumat:
- Di mana untuk mula mengikis
- Bagaimana untuk menghuraikan respons
Anda boleh memberikan maklumat ini menggunakan start_urls
dan .parse()
, masing-masing. start_urls
ialah senarai rentetan yang mengandungi hanya satu URL dalam kes anda, tetapi boleh mengandungi lebih banyak lagi. Rangka kerja mengikis web kegemaran anda telah mengisi nilai ini, dan nama labah-labah, apabila anda menyediakan perancah labah-labah menggunakan genspider
.
Kaedah .parse()
Spider
ialah kaedah panggil balik yang Scrapy panggil dengan objek respons
yang dimuat turun untuk setiap URL. Ia harus mengandungi logik untuk mengekstrak maklumat yang berkaitan daripada respons. Anda boleh bekerjasama dengan pemilih CSS yang anda kenal pasti sebelum ini untuk mengekstrak maklumat daripada respons, kemudian gunakan BooksItem
anda untuk mengumpul data:
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
Anda telah mencipta labah-labah Scrapy dengan hanya beberapa baris kod! Perhatikan bagaimana anda menggunakan semula kod yang anda tulis dalam shell. Anda hanya perlu mengumpul maklumat ke dalam contoh BooksItem
, yang anda lakukan menggunakan sintaks kamus. Akhir sekali, anda menggunakan hasil
untuk menjana setiap item.
Nota: Menggunakan hasil
menukarkan .parse()
menjadi penjana yang menghasilkan keputusannya dan bukannya mengembalikannya. Ini membolehkan rangka kerja mengendalikan berbilang permintaan dan respons secara serentak.
Scrapy dibina di atas rangka kerja Twisted, yang merupakan enjin rangkaian dipacu peristiwa yang membenarkan permintaan tak segerak. Ini menjadikan proses mengikis lebih cekap dan berskala.
Anda menyediakan .parse()
supaya ia mula-mula menemui semua buku pada halaman semasa dan kemudian berulang pada setiap buku. Untuk setiap buku, ia mencipta contoh BooksItem
dan mengekstrak URL, tajuk dan harga menggunakan pemilih CSS. Ia memberikan nilai ini kepada medan masing-masing BooksItem
anda dan kemudian menghasilkan contoh item kepada saluran paip item.
Dalam kes ini, Python akan memanggil .parse()
sekali sahaja, tetapi jika anda sedang mengusahakan projek mengikis yang lebih besar yang melibatkan berbilang start_urls
, maka rangka kerja akan memanggilnya secara automatik untuk setiap daripada mereka.
Memandangkan labah-labah anda telah selesa menetap di webnya yang baru dibina, anda boleh menghantarnya untuk mengambil data daripada Internet.
Ekstrak Data Dari Laman Web
Anda telah selesai menyatukan labah-labah anda. Untuk menjalankannya dan melihat data yang dikikis, buka terminal anda dan sahkan bahawa anda masih berada dalam direktori projek Scrapy anda. Anda boleh memulakan labah-labah dari sana menggunakan perintah crawl
:
(venv) $ scrapy crawl book
Scrapy akan mula merangkak URL yang ditentukan. Ia akan mencetak banyak maklumat pengelogan ke terminal anda. Bersarang di antara log, anda juga harus melihat data yang diekstrak untuk setiap buku pada halaman pendaratan yang dicetak:
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)
Hebat! Anda berjaya mengikis dan mengekstrak semua URL, tajuk dan maklumat harga daripada halaman pertama kedai buku palsu! Walau bagaimanapun, itu hanya halaman satu daripada banyak.
Anda mungkin tertanya-tanya sama ada anda perlu menambah setiap halaman pada start_url
atau menyediakan gelung for
untuk mengulangi semua halaman yang tersedia. Jangan risau, ada cara yang lebih mantap dan mudah yang telah terbina dalam rangka kerja!
Mengendalikan Penomboran dan Ikuti URL
Banyak tapak web memaparkan data merentas berbilang halaman. Jika labah-labah anda boleh mengendalikan penomboran dan mengikuti URL, maka ia boleh menavigasi berbilang halaman tapak web dan mengekstrak data daripada setiap satu. Dalam Scrapy, anda hanya perlu menambahkan sedikit kod pada labah-labah anda supaya ia boleh menangani penomboran dan merangkak tapak web sasaran anda dengan betul.
Merangkak web bermaksud menyemak imbas Web secara sistematik untuk mengindeks dan mengumpul maklumat. Fungsi utama perangkak web adalah untuk mengikuti pautan pada halaman web untuk menemui kandungan baharu dan mengumpul data. Dalam konteks pengikisan web, perangkak boleh menavigasi melalui halaman yang berbeza pada tapak web, mengekstrak maklumat yang berkaitan dan menyimpannya untuk kegunaan selanjutnya.
Tetapi sebelum anda boleh mengemas kini labah-labah anda, anda perlu memahami cara tapak web mengendalikan penomboran. Buka pelayar anda atau shell Scrapy dan periksa tapak web untuk mencari kawalan penomboran.
Dalam laman web Books to Scrape, anda akan menemui pautan penomboran di bahagian bawah halaman:
<li class="next"><a href="catalogue/page-2.html">next</a></li>
Butang seterusnya memaut ke halaman hasil seterusnya. Ia dilaksanakan sebagai item senarai (<li>
) dengan kelas yang dipanggil "next"
yang mengandungi elemen pautan (<a>
) dengan URL halaman seterusnya. Apabila anda mengkliknya, anda akan melihat bahawa tapak memuatkan halaman seterusnya dan memaparkan item buku yang berbeza.
Dengan pengetahuan itu, kembali ke book.py
dan ubah suai BookSpider
sedia ada anda untuk mengendalikan penomboran dan mengikis data daripada semua halaman hasil:
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)
Anda bermula dengan menyasarkan atribut href
bagi elemen yang berkaitan dan menyimpan nilainya ke next_page
. Walau bagaimanapun, URL itu bukanlah URL yang layak sepenuhnya, jadi anda perlu menggabungkannya dengan URL asas sebelum Scrapy boleh menghantar permintaan baharu. Anda melakukannya menggunakan .urljoin()
di dalam blok bersyarat. Akhir sekali, anda menghasilkan objek scrapy.Request
, memberikannya URL yang baru anda gabungkan dan menggunakan kaedah .parse()
sebagai panggilan balik.
Persediaan ini membenarkan rangka kerja membuat permintaan lain ke halaman kedua kedai buku, sekali lagi menggunakan kaedah .parse()
yang anda tulis, dengan itu meneruskan proses mengikis. Proses ini akan diteruskan secara rekursif sehingga tiada lagi pautan seterusnya, dengan berkesan mengikis semua halaman tapak web.
Labah-labah anda kini disediakan untuk mengendalikan penomboran. Anda boleh menjalankannya semula untuk mengikis data daripada semua halaman:
(venv) $ scrapy crawl book
Scrapy akan bermula pada halaman pertama, mengekstrak data yang dikehendaki, mengikuti pautan penomboran, dan terus mengekstrak data dari halaman berikutnya sehingga ia mencapai halaman terakhir. Output dalam terminal anda akan kelihatan sama seperti sebelumnya, cuma ia akan mengambil masa yang lebih lama dan memberi anda maklumat buku untuk semua buku yang terdapat di kedai buku!
Mengendalikan penomboran dan URL berikut adalah kemahiran penting untuk membina perangkak web yang berkesan. Dengan memasukkan logik penomboran ke dalam labah-labah anda, anda memastikan pengikis web anda boleh menavigasi berbilang halaman dan mengekstrak data komprehensif daripada tapak web sasaran. Pendekatan ini membolehkan anda membina pengikis yang lebih berkuasa dan fleksibel yang boleh mengendalikan pelbagai laman web.
Simpan Data Dikikis dalam MongoDB
Anda telah membina labah-labah yang berfungsi yang merangkak seluruh kedai buku, mengikut pautan penomboran untuk mengekstrak semua data yang anda perlukan. Pada masa ini, Scrapy menghantar data tersebut ke aliran ralat standard (stderr) yang anda dapat lihat dalam terminal anda.
Anda boleh menulis kod untuk menyimpannya ke fail JSON, tetapi anda sedang membina pengikis yang sepatutnya boleh mengendalikan sejumlah besar data dan terus berfungsi lama ke masa hadapan.
Oleh sebab itu, selalunya idea yang baik untuk menyimpan data yang anda cari dalam pangkalan data. Pangkalan data boleh membantu anda menyusun dan mengekalkan maklumat dengan selamat dan mudah. MongoDB ialah pilihan terbaik untuk mengendalikan data yang dikikis daripada Internet kerana fleksibiliti dan kapasitinya untuk mengendalikan data dinamik dan separa berstruktur. Dalam bahagian ini, anda akan mempelajari cara anda boleh menyimpan data yang dikikis dalam koleksi MongoDB.
Sediakan Koleksi MongoDB pada Komputer Anda
Sebelum anda boleh mula menggunakan MongoDB, anda perlu memasangnya pada sistem anda. Proses pemasangan berbeza bergantung pada sistem pengendalian anda, jadi pastikan anda mengikuti arahan yang sesuai untuk sistem pengendalian anda.
Nota: Untuk arahan yang lebih terperinci dan pilihan konfigurasi tambahan, anda boleh merujuk kepada Pengenalan menyeluruh kepada MongoDB dan Python.
Selepas anda berjaya memasang MongoDB, anda boleh mengesahkan pemasangan anda menggunakan terminal anda:
$ mongod --version
db version v7.0.12
Build Info: {
"version": "7.0.12",
"gitVersion": "b6513ce0781db6818e24619e8a461eae90bc94fc",
"modules": [],
"allocator": "system",
"environment": {
"distarch": "aarch64",
"target_arch": "aarch64"
}
}
Output tepat yang akan anda peroleh bergantung pada versi yang anda pasang, serta sistem pengendalian anda. Tetapi ia akan kelihatan serupa dengan output yang anda boleh lihat di atas. Jika terminal anda memberi anda mesej ralat, maka anda perlu menyemak semula pemasangan anda dan cuba lagi.
Jika ini kali pertama anda menggunakan MongoDB, maka anda mungkin perlu melaksanakan beberapa tugas persediaan untuk menyediakannya untuk menyimpan data.
Secara lalai, MongoDB menyimpan data dalam /data/db
. Anda mungkin perlu mencipta direktori ini dan memastikan ia mempunyai kebenaran yang betul. Contohnya:
$ sudo mkdir -p /data/db
$ sudo chown -R `id -u` /data/db
Jika MongoDB belum lagi berjalan, maka anda boleh memulakannya dengan perintah mongod
. Ini akan memulakan pelayan MongoDB dan mengikatnya pada port lalai 27017
.
Untuk berinteraksi dengan contoh MongoDB anda, anda boleh menggunakan cangkerang MongoDB dengan menjalankan mongosh
dalam terminal anda. Ini akan menghubungkan anda ke pelayan MongoDB dan membolehkan anda melakukan operasi pangkalan data:
$ 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>
Dalam cangkerang MongoDB, anda ingin mencipta pangkalan data baharu dengan koleksi baharu. Anda akan memanggil pangkalan data books_db
dan koleksi books
:
test> use books_db
switched to db books_db
books_db> db.createCollection("books")
{ ok: 1 }
books_db> show collections
books
books_db>
Dengan pangkalan data dan koleksi baharu anda, anda bersedia untuk menyelam kembali ke Scrapy untuk menyambungkan labah-labah anda ke MongoDB. Anda akan menyediakannya supaya semua data yang dikikis akan masuk ke dalam koleksi buku
baharu anda.
Sambung ke Pangkalan Data MongoDB Dari Scrapy
Anda akan menggunakan pustaka pihak ketiga pymongo
untuk menyambung ke pangkalan data MongoDB anda dari dalam projek Scrapy anda. Mula-mula, anda perlu memasang pymongo
daripada PyPI:
(venv) $ python -m pip install pymongo
Selepas pemasangan selesai, anda sudah bersedia untuk menambah maklumat tentang contoh MongoDB anda pada projek Scrapy anda.
Apabila anda mencipta projek anda, anda juga mendapat fail bernama settings.py
. Ia merupakan tempat pusat yang membolehkan anda menentukan tetapan untuk projek anda, dan ia sudah mengandungi beberapa maklumat. Ia juga mengandungi banyak nota tentang tetapan tambahan yang boleh anda gunakan untuknya.
Satu kes penggunaan untuk settings.py
ialah untuk menyambungkan rangka kerja mengikis web ke pangkalan data. Buka settings.py
dan tambahkan butiran sambungan MongoDB di bahagian bawah fail:
# ...
MONGO_URI = "mongodb://localhost:27017"
MONGO_DATABASE = "books_db"
Anda kemudiannya akan memuatkan pembolehubah ini ke dalam saluran paip anda dan menggunakannya untuk menyambung ke pangkalan data books_db
yang dijalankan pada komputer setempat anda. Jika anda menyambung ke contoh MongoDB yang dihoskan, maka anda perlu menyesuaikan maklumat dengan sewajarnya.
Memproses Data Melalui Talian Paip Scrapy
Talian paip item Scrapy ialah ciri berkuasa yang membolehkan anda memproses data yang dikikis sebelum anda menyimpannya atau memprosesnya dengan lebih lanjut. Saluran paip memudahkan pelbagai tugas pasca pemprosesan seperti pembersihan, pengesahan dan penyimpanan. Dalam bahagian ini, anda akan membuat saluran paip item untuk menyimpan data yang dikikis dalam koleksi MongoDB baharu anda, menggemakan proses muatan aliran kerja ETL.
Talian paip item ialah kelas Python yang mentakrifkan beberapa kaedah untuk memproses item selepas labah-labah anda mengikisnya daripada Internet:
.open_spider()
dipanggil apabila labah-labah dibuka..close_spider()
dipanggil apabila labah-labah ditutup..process_item()
dipanggil untuk setiap komponen saluran paip item. Ia mesti sama ada mengembalikan item atau menaikkan pengecualianDropItem
..from_crawler()
mencipta saluran paip daripadaCrawler
untuk menjadikan tetapan projek umum tersedia kepada saluran paip.
Anda akan melaksanakan semua kaedah ini semasa membina saluran paip tersuai anda untuk memasukkan item yang dikikis ke dalam koleksi MongoDB anda. Walaupun memuatkan data ke dalam pangkalan data ialah satu kemungkinan kes penggunaan saluran paip item, anda juga boleh menggunakannya untuk tujuan lain, seperti membersihkan data HTML atau mengesahkan data yang dikikis.
Nota: Dari perspektif proses ETL, saluran paip item boleh berfungsi untuk kedua-dua operasi transform dan load.
Anda telah memasang MongoDB dan pymongo
, mencipta koleksi dan menambah tetapan khusus MongoDB pada fail settings.py
projek anda. Seterusnya, anda akan menentukan kelas saluran paip dalam books/pipelines.py
. Saluran paip ini akan menyambung ke MongoDB dan memasukkan item yang dikikis ke dalam koleksi:
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
Anda telah menambah apa yang mungkin kelihatan seperti sebahagian besar kod. Walau bagaimanapun, kebanyakannya adalah kod boilerplate. Inilah yang dilakukan oleh setiap bahagian:
itemadapter
membalut bekas data yang berbeza untuk mengendalikannya dengan cara yang seragam. Pakej itu dipasang sebagai kebergantungan Scrapy.COLLECTION_NAME
menentukan nama koleksi MongoDB tempat anda ingin menyimpan item. Ini sepatutnya sepadan dengan nama koleksi yang anda sediakan sebelum ini..__init__()
memulakan saluran paip dengan URI MongoDB dan nama pangkalan data. Anda boleh mengakses maklumat ini kerana anda mengambilnya daripadaCrawler
menggunakan kaedah kelas.from_crawler()
..from_crawler()
ialah kaedah kelas yang memberi anda akses kepada semua komponen Scrapy teras, seperti tetapan. Dalam kes ini, anda menggunakannya untuk mendapatkan semula tetapan MongoDB daripadasettings.py
melalui perangkak Scrapy..open_spider()
membuka sambungan ke MongoDB apabila labah-labah bermula..close_spider()
menutup sambungan MongoDB apabila labah-labah selesai..process_item()
memasukkan setiap item yang dikikis ke dalam koleksi MongoDB. Kaedah ini biasanya mengandungi fungsi teras saluran paip.
Dengan MongoPipeline
anda disediakan, anda masih perlu mengaktifkannya dalam konteks projek anda supaya rangka kerja akan menggunakannya pada larian seterusnya. Untuk mendayakan saluran paip, anda perlu menambahkannya pada tetapan projek anda dalam settings.py
.
Apabila anda menjana perancah projek Scrapy menggunakan projek permulaan scrapy
, fail settings.py
anda sudah termasuk masukan untuk ITEM_PIPELINES
. Seperti kebanyakan tetapan dalam fail itu, ia dikomentari. Anda boleh mencari entri itu dan mengalih keluar ulasan Python, kemudian tambahkan lokasi nama yang layak bagi objek saluran paip baharu anda:
ITEM_PIPELINES = {
"books.pipelines.MongoPipeline": 300,
}
Anda boleh menambah saluran paip pada projek anda sebagai entri dalam kamus, dengan nama yang layak bagi kelas saluran paip anda ialah kuncinya dan integer ialah nilainya. Integer menentukan susunan di mana Scrapy menjalankan saluran paip. Nombor yang lebih rendah bermakna keutamaan yang lebih tinggi.
Nota: Menggunakan 300
ialah konvensyen yang menunjukkan kepada pembangun bahawa nombor ini adalah fleksibel dan bahawa mereka boleh menggunakan julat yang luas di atas dan di bawahnya untuk menyusun keutamaan saluran paip anda.
Dengan menentukan saluran paip item, anda boleh menyepadukan kedua-dua operasi transform dan load dengan lancar ke dalam tugas mengikis web anda. Dengan sambungan MongoDB anda disediakan dalam projek anda, sudah tiba masanya untuk menjalankan pengikis pada masa lain dan menilai sama ada ia berfungsi seperti yang diharapkan.
Elakkan Menambah Entri Pendua
Apabila anda menjalankan pengikis anda pada kali pertama, semuanya akan berfungsi seperti yang diharapkan. Scrapy mengisi koleksi MongoDB anda dengan maklumat daripada tapak web. Jika anda menyemak panjang koleksi anda selepas larian awal dalam cangkerang Mongo, maka anda akan melihat bahawa ia mengandungi 1000
dokumen:
books_db> db.books.countDocuments()
1000
Walau bagaimanapun, jika anda menjalankan pengikis lain kali, maka pangkalan data buku anda telah bertambah dua kali ganda panjangnya dan kini memegang dua ribu dokumen. Masalah ini wujud kerana secara lalai, MongoDB mencipta ID unik untuk setiap dokumen berdasarkan cap masa, antara maklumat lain. Oleh itu, ia akan menganggap setiap item yang dikikis sebagai dokumen baharu dan memberikannya ID baharu, yang membawa kepada pertindihan data dalam pangkalan data anda.
Ini mungkin perkara yang anda mahukan dalam sesetengah projek, tetapi di sini anda mahu menyimpan setiap item buku yang dikikis sekali sahaja, walaupun anda menjalankan pengikis beberapa kali. Untuk itu, anda perlu mencipta dan menentukan medan ID unik untuk data anda yang boleh digunakan oleh MongoDB untuk mengenal pasti setiap dokumen.
Buka books/pipelines.py
sekali lagi dan tambah logik yang diperlukan untuk menyemak pendua berdasarkan medan id
unik baharu yang anda akan perolehi daripada pencincangan URL halaman individu:
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()
Untuk mengelakkan pengumpulan elemen pendua pada berbilang larian pengikis, anda telah menggunakan kemas kini pada .process_item()
dan menambahkan kaedah pembantu baharu .compute_item_id()
:
Barisan 1 dan 4 memegang dua import baharu, satu untuk modul
hashlib
perpustakaan standard yang anda gunakan untuk memproses URL menjadi pengecam unik dalam.compute_item_id()
. Import kedua membawaDropItem
daripadascrapy.exceptions
, yang merupakan cara cepat untuk menggugurkan item pendua dan menulis ke log Scrapy.Barisan 28 memanggil
.compute_item_id()
dan memberikan output cincang kepadaitem_id
.Barisan 29 dan 30 menanyakan koleksi MongoDB untuk menyemak sama ada item dengan
_id
yang sama sudah wujud. Jika Python menemui pendua, maka kod itu menimbulkan pengecualianDropItem
, yang memberitahu rangka kerja untuk membuang item ini dan tidak memprosesnya lagi. Jika ia tidak menemui pendua, maka ia meneruskan ke langkah seterusnya.Barisan 31 hingga 34 membentuk keadaan
lain
, di mana item yang dikikis belum lagi wujud dalam pangkalan data. Kod ini hampir sama dengan kod yang anda miliki sebelum menambah logik deduplikasi, namun dengan perbezaan penting. Sebelum memasukkan item ke dalam koleksi anda, anda menambahitem_id
yang dikira sebagai nilai untuk atribut._id
padaItem
anda. MongoDB menggunakan medan bernama_id
sebagai kunci utama, jika ada.
Perhatikan bahawa sama seperti sebelumnya, kaedah mengembalikan item. Ini membolehkan pemprosesan lanjut oleh saluran paip lain jika anda membina sebarang.
Sebelum anda boleh menjalankan saluran paip anda yang dikemas kini, anda perlu mengambil kira medan _id
baharu pada BooksItem
. Buka books/items.py
dan tambahkannya pada definisi item anda:
import scrapy
class BooksItem(scrapy.Item):
_id = scrapy.Field()
url = scrapy.Field()
title = scrapy.Field()
price = scrapy.Field()
Anda tidak perlu menukar apa-apa dalam labah-labah anda dan anda kini mengisi setiap BooksItem
dari dua tempat berbeza dalam kod anda:
- Labah-labah anda menambah nilai untuk
.url
,.title
dan.price
padaBooksItem
. - Talian paip item anda menambah nilai untuk
._id
, selepas mengiranya daripada.url
.
Itulah contoh hebat kuasa dan fleksibiliti yang menggunakan proses terbina dalam Scrapy menawarkan anda semasa membina pengikis web anda.
Seperti yang dinyatakan secara ringkas, .compute_item_id()
menggunakan hashlib
Python untuk mencincang nilai URL. Menggunakan URL cincang sebagai pengecam unik bermakna Python tidak akan menambahkan mana-mana buku yang anda kikis dua kali daripada URL yang sama ke koleksi anda.
Perkara yang anda anggap sebagai pengecam unik ialah keputusan reka bentuk yang anda perlu fikirkan secara individu untuk setiap projek. Dalam kes ini, menggunakan URL cincang berfungsi dengan baik kerana anda bekerja dengan kedai buku palsu yang setiap sumber buku mempunyai URL unik. Walau bagaimanapun, jika data untuk mana-mana buku ini berubah—sambil mengekalkan URL yang sama—maka pelaksanaan semasa akan menggugurkan item tersebut.
Jika menjatuhkan item dalam kes sedemikian bukan tingkah laku yang dimaksudkan, maka anda boleh mengemas kini kod anda dengan menggunakan operasi upsert. Dalam MongoDB, anda boleh melakukan upserts menggunakan db.collection.updateOne()
dengan upsert
ditetapkan kepada benar. pymongo
menterjemah operasi ini ke dalam kod Python:
# ...
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
# ...
Apabila anda menggunakan persediaan ini, maka anda tidak perlu mengemas kini BooksItem
anda kerana ia memintas penyesuai item dan mencipta _id
dokumen secara langsung apabila berinteraksi dengan MongoDB.
Apabila anda menggunakan .update_one()
dengan pymongo
, maka anda perlu menghantar dua argumen utama:
- penapis untuk mencari dokumen dalam koleksi anda
- operasi kemas kini
Argumen ketiga yang anda berikan, upsert=True
, memastikan MongoDB akan memasukkan item jika ia tidak wujud. Seperti yang dinyatakan, anda tidak perlu menambah secara eksplisit nilai untuk _id
pada BooksItem
anda, kerana .update_one()
mengambil nilai tersebut daripada tapis hujah dan hanya menggunakannya untuk mencipta dokumen dalam koleksi anda.
Anda perlu menggugurkan koleksi sedia ada anda, yang masih mengandungi ID berasaskan cap waktu, sebelum kod ini mempunyai kesan yang dimaksudkan:
books_db> db.books.drop()
true
books_db> db.books.countDocuments()
0
Kini anda boleh meneruskan dan menjalankan pengikis anda beberapa kali lagi, kemudian semak jumlah dokumen yang anda ada dalam koleksi anda. Jumlahnya hendaklah sentiasa kekal seribu.
Perlu diingat bahawa pendekatan yang anda pilih mungkin bergantung pada projek yang sedang anda usahakan. Sama ada cara, anda kini mempunyai idea yang lebih baik bagaimana anda boleh menggugurkan item pendua dan mengemas kini entri sedia ada dengan data baharu.
Nyahpepijat dan Uji Pengikis Web Scrapy Anda
Anda mungkin akan menghadapi situasi di mana pengikis anda tidak melakukan apa yang anda harapkan. Projek mengikis web sentiasa berurusan dengan pelbagai faktor yang boleh membawa kepada hasil yang tidak dijangka. Penyahpepijatan mungkin akan menjadi bahagian penting dalam proses pembangunan pengikis anda. Scrapy menyediakan beberapa alat berkuasa untuk membantu anda menyahpepijat labah-labah anda dan memastikan ia berfungsi seperti yang dimaksudkan.
Ujian menulis untuk labah-labah anda memastikan kod anda akan terus berfungsi seperti yang diharapkan. Pengujian adalah bahagian penting dalam sebarang proses pembangunan perisian, dan pengikisan web tidak terkecuali.
Dalam bahagian ini, anda akan membincangkan cara menggunakan alat penyahpepijat dan ujian Scrapy, termasuk ciri pengelogan, cangkang Scrapy, fungsi panggil balik ralat dan kontrak labah-labah. Anda juga akan menulis beberapa ujian unit tersuai untuk mengesahkan bahawa labah-labah anda berfungsi seperti yang diharapkan.
Log Maklumat Dengan Logger
Pembalakan ialah alat asas untuk memahami apa yang labah-labah anda lakukan pada setiap langkah. Secara lalai, rangka kerja merekodkan pelbagai acara, tetapi anda boleh menyesuaikan pengelogan agar sesuai dengan keperluan anda. Scrapy menggunakan pustaka logging
Python yang datang sebagai sebahagian daripada pustaka standard. Anda tidak perlu mengimportnya, kerana objek labah-labah sudah mempunyai akses kepada pembalak.
Buka fail book.py
anda dan tambahkan pengelogan untuk menjejaki apabila labah-labah anda menavigasi ke halaman baharu:
# ...
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)
# ...
Anda telah menambahkan kod yang mengakses .logger
daripada BookSpider
anda dan log maklumat apabila labah-labah menavigasi ke halaman baharu dengan tahap pengelogan INFO.
Secara lalai, rangka kerja dilog dengan keterukan DEBUG, yang boleh membantu untuk penyahpepijatan, tetapi berpotensi menggembirakan apabila anda melihatnya pada setiap larian. Memandangkan anda telah menambahkan mesej nyahpepijat dengan tahap keterukan yang lebih tinggi, anda boleh beralih ke tahap itu untuk hanya melihat mesej INFO dan keterukan yang lebih tinggi yang dicetak pada konsol anda.
Anda boleh mengkonfigurasi log masuk labah-labah anda secara terus melalui pemalar dalam settings.py
:
# ...
LOG_LEVEL = "INFO"
Selepas anda menambahkan baris ini pada settings.py
, jalankan pengikis anda pada lain masa. Anda tidak akan melihat sebarang maklumat nyahpepijat, tetapi hanya log keterukan INFO dan lebih tinggi.
Jika anda menjalankan labah-labah semasa mengisi koleksi kosong, maka anda hanya akan mendapat maklumat yang dilayari Scrapy ke halaman lain.
Walau bagaimanapun, anda akan melihat output yang berbeza jika anda menjalankannya untuk kali kedua, bergantung pada logik penyahduplikasi yang anda putuskan untuk disimpan dalam MongoPipeline.process_item()
:
Jika anda menggunakan upserts dan
.update_one()
pymongo, maka anda hanya akan melihat mesej log apabila Scrapy menavigasi ke halaman baharu.Jika anda masih menggunakan medan
._id
danDropItem
eksplisit, maka anda akan terharu dengan log AMARAN. Scrapy secara automatik menulis AMARAN apabila ia menangkap pengecualianDropItem
.
Mengelog semua amaran mungkin idea yang baik, dan anda mungkin mahu menyimpan fail log di sekeliling untuk pemeriksaan kemudian. Anda boleh menetapkan nilai untuk LOG_FILE
dalam tetapan Scrapy untuk menulis pada fail dan bukannya stderr, dan menyesuaikan LOG_LEVEL
sekali lagi supaya Python hanya menulis amaran kepada fail log:
# ...
LOG_LEVEL = "WARNING"
LOG_FILE = "book_scraper.log"
Dengan dua tetapan ini, anda memperibadikan tahap pengelogan dan lokasi fail log dalam projek Scrapy anda. Menetapkan LOG_LEVEL
kepada "AMARAN"
bermakna Python hanya akan log mesej dengan keterukan yang lebih tinggi. Tetapan LOG_FILE
menentukan fail di mana Scrapy akan menyimpan log.
Jika anda kini menjalankan labah-labah anda, anda tidak akan melihat sebarang output dalam konsol anda. Sebaliknya, fail baharu muncul di mana Scrapy menulis maklumat tentang semua item yang digugurkan—dengan mengandaikan bahawa anda sedang bekerja dengan kod yang menggunakan 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'}
...
Jika anda bekerja dengan upserts, maka anda akan melihat fail log kosong. Baik untuk mengetahui bahawa tiada apa yang berlaku!
Pengelogan boleh membantu anda menjejaki aliran labah-labah anda, memeriksa nilai pembolehubah dan mengenal pasti di mana perkara mungkin berlaku. Selalunya idea yang baik untuk menetapkan tahap pengelogan kepada DEBUG dan menulis log pada fail, untuk membolehkan anda menjejaki semula tindakan labah-labah anda sekiranya ia gagal dalam larian.
Tangani Ralat Dengan errback
Scrapy membolehkan anda mengendalikan ralat dengan anggun menggunakan fungsi panggil balik ralat. Jika anda menghantar kaedah kepada parameter errback
objek Request
, maka Scrapy akan menggunakannya untuk mengendalikan pengecualian. Ini amat membantu apabila menangani ralat rangkaian atau respons pelayan yang tidak dijangka, dan anda boleh menggunakannya untuk log ralat tanpa menamatkan program anda.
Buka book.py
dan tambahkan kaedah baharu pada labah-labah anda untuk mengendalikan ralat:
import scrapy
from books.items import BooksItem
class BookSpider(scrapy.Spider):
# ...
def log_error(self, failure):
self.logger.error(repr(failure))
Dalam .log_error()
, anda menangkap pengecualian dan logkannya dengan tahap keterukan ERROR. Ini boleh membantu anda dalam mendiagnosis isu tanpa menghentikan pelaksanaan labah-labah.
Seterusnya, anda perlu menyepadukan kaedah baharu anda ke dalam kod. Untuk menangkap ralat yang berlaku semasa membuat permintaan, anda perlu menentukan parameter errback
sebagai tambahan kepada callback
. Dengan cara itu, anda boleh memastikan bahawa Scrapy mengendalikan ralat menggunakan kaedah .log_error()
anda:
# ...
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,
)
Dengan menghantar nama kaedah panggil balik ralat anda kepada scrapy.Request
, anda memastikan bahawa sebarang permintaan untuk halaman selanjutnya kedai buku yang mengembalikan ralat akan direkodkan.
Jika anda juga ingin menggunakan .log_error()
untuk permintaan awal kepada Books to Scrape, maka anda perlu memfaktorkan semula kod anda untuk menggunakan .start_requests()
dan bukannya hanya bergantung pada .start_urls
:
# ...
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):
# ...
Dengan menambahkan .start_requests()
, anda memperoleh fleksibiliti tambahan tentang cara mengendalikan permintaan awal pada setiap URL dalam start_urls
. Scrapy hanya memanggil kaedah ini sekali untuk setiap URL permulaan. Dalam contoh ini, anda menambahkan kaedah .log_error()
baharu anda juga pada permintaan awal itu dengan menghantarnya kepada errback
. Jika anda menentukan pengendalian ralat anda sendiri dengan errback
, maka anda boleh menyelesaikan masalah dan memperhalusi keadaan ralat dengan cekap dalam labah-labah mengikis web anda.
Selepas anda meneroka beberapa alatan penyahpepijatan yang ditawarkan oleh Scrapy, anda kini akan melihat cara anda boleh menulis ujian untuk memastikan pengikis anda berfungsi seperti yang dimaksudkan.
Tandatangani Beberapa Kontrak Labah-labah
Scrapy menawarkan ciri yang dikenali sebagai kontrak, membolehkan anda membenamkan kes ujian secara langsung dalam docstring labah-labah anda. Sama seperti doctest
Python, ciri ini membolehkan pengesahan pantas kaedah labah-labah, memastikan ia berfungsi seperti yang diharapkan tanpa memerlukan persediaan ujian yang meluas.
Kontrak labah-labah pada asasnya ialah penegasan yang boleh anda letakkan dalam docstring kaedah labah-labah anda. Mereka mentakrifkan tingkah laku yang dijangkakan kaedah:
@url
menentukan URL yang Scrapy harus ambil.@returns
menunjukkan bilangan item yang dijangkakan atau permintaan yang harus dipulangkan oleh labah-labah.@scrapes
menyenaraikan medan yang sepatutnya mengandungi item.
Buka fail book.py
anda dan tambahkan kontrak pada kaedah .parse()
anda:
# ...
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"):
# ...
Anda telah menambah empat kontrak pada docstring .parse()
:
@url https://books.toscrape.com
memberitahu Scrapy untuk mengambil URL yang ditentukan untuk menguji kaedah penghuraian. Kontrak ini diperlukan untuk mana-mana ujian lain dijalankan.@returns item 20 20
menyatakan bahawa kaedah harus mengembalikan tepat dua puluh item. Nombor pertama ialah item jangkaan minimum, dan yang kedua ialah maksimum.@returns request 1 50
bermakna.parse()
harus menjana sekurang-kurangnya satu dan paling banyak lima puluh permintaan. Bahagian ini berkaitan dengan kod penomboran yang anda sediakan.@scrapes url title price
menyatakan bahawa setiap item yang dikembalikan harus mengandungi medanurl
,title
danprice
.
Dengan hanya empat baris ini, anda telah menyediakan ujian asas untuk .parse()
yang boleh membantu anda mengesan masalah dengan pengikis anda lebih awal.
Nota: Terdapat satu lagi kontrak terbina dalam, @cb_kwargs
, yang anda tidak gunakan dalam kes ini, tetapi ini mungkin membantu apabila anda perlu menetapkan hujah kata kunci untuk permintaan sampel. Anda juga boleh menulis kontrak tersuai.
Untuk melaksanakan ujian kontrak, anda boleh menggunakan perintah check
dalam terminal anda:
(venv) $ scrapy check book
...
----------------------------------------------------------------------
Ran 3 contracts in 1.399s
OK
Apabila menjalankan perintah ini, Scrapy mengambil URL yang ditentukan, melaksanakan .parse()
dan mengesahkan keputusan terhadap penegasan dalam docstring. Jika mana-mana pernyataan gagal, maka rangka kerja akan mengeluarkan mesej ralat yang menunjukkan perkara yang salah.
Ambil perhatian bahawa kontrak @url
adalah prasyarat untuk kontrak lain berjalan dengan jayanya. Scrapy tidak menyenaraikannya sebagai syarat ujian dalam output, itulah sebabnya mesej membaca bahawa Scrapy menjalankan tiga kontrak.
Jika mana-mana kontrak gagal, Scrapy akan memberikan butiran tentang kegagalan tersebut, seperti bilangan item yang tidak memenuhi jangkaan yang ditetapkan. Teruskan dan edit kontrak anda untuk memeriksa rupa ujian yang gagal. Jika anda biasa dengan modul unittest
Python, maka anda mungkin akan mengenali outputnya. Di bawah tudung, kontrak menggunakan unittest
untuk mengatur dan melaksanakan ujian.
Ringkasnya, kontrak labah-labah datang dengan beberapa faedah, seperti:
Pengesahan Pantas: Kontrak menyediakan cara cepat untuk mengesahkan labah-labah anda tanpa menyediakan rangka kerja ujian yang meluas. Ini amat berguna semasa pembangunan apabila anda ingin memastikan kaedah labah-labah anda berfungsi dengan betul.
Dokumentasi: Rentetan dokumen dengan kontrak berfungsi sebagai dokumentasi, menjelaskan dengan jelas apakah gelagat yang dijangkakan bagi kaedah labah-labah anda. Ini boleh memberi manfaat kepada ahli pasukan atau apabila menyemak semula kod selepas beberapa ketika.
Ujian Automatik: Kontrak boleh disepadukan ke dalam saluran paip ujian automatik anda, memastikan labah-labah anda terus berfungsi dengan betul semasa anda membuat perubahan pada pangkalan kod anda.
Dengan memasukkan kontrak labah-labah ke dalam proses pembangunan anda, anda boleh meningkatkan kebolehpercayaan dan kebolehselenggaraan pengikis web anda, memastikan ia berfungsi seperti yang diharapkan dalam pelbagai keadaan.
Tulis Ujian Unit untuk Ujian Terperinci
Kontrak labah-labah ialah cara terbaik untuk membuat ujian pantas. Walau bagaimanapun, jika anda bekerja dengan pengikis yang kompleks dan anda ingin menyemak fungsinya dengan lebih terperinci, adalah idea yang baik untuk menulis ujian unit untuk menguji komponen individu.
Nota: Pilihan popular untuk ujian unit dalam Python ialah pustaka pihak ketiga pytest
. Walau bagaimanapun, anda akan terus bekerja dengan pustaka unittest
terbina dalam Python. Scrapy menggunakannya untuk kontrak, dan hei—ia sudah dibungkus dengan Python!
Untuk menulis ujian unit bagi kaedah BookSpider
, anda perlu mensimulasikan persekitaran Scrapy. Ini melibatkan respons mengejek dan permintaan untuk memastikan kaedah labah-labah anda berkelakuan seperti yang diharapkan.
Di dalam folder projek books/
peringkat atas anda, buat folder tests/
. Di dalam folder itu, buat dua fail:
__init__.py
memulakan folder sebagai pakej Python.test_book.py
akan mengadakan ujian unit untuk pengikis anda.
Sekarang, luangkan masa untuk berfikir tentang aspek pengikis anda yang ingin anda uji. Mungkin berguna untuk mempertimbangkan semula perkara yang telah dilindungi oleh kontrak labah-labah anda, kemudian bina di atasnya dengan kekhususan tambahan.
Contohnya, walaupun terdapat kontrak yang menyemak bilangan item yang dikikis setiap halaman dan elemen yang diekstrak Scrapy, anda tidak mempunyai sebarang semakan untuk sama ada ini adalah item betul. Kontrak anda tidak mengetahui apa-apa tentang kandungan unsur yang diekstraknya. Ia boleh menjadi idea yang baik untuk menulis ujian unit untuk mengisi jurang itu.
Selain itu, anda mungkin ingin mengesahkan bahawa setiap panggilan ke .parse()
mengenal pasti elemen yang betul daripada HTML, mengambil semua buku dan membuat permintaan baharu untuk URL penomboran:
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()
Dengan niat anda ditetapkan dan struktur kelas ujian asas disediakan, anda boleh meneruskan dan menulis kod ujian anda. Ujian anda selalunya akan mendapat manfaat daripada kaedah .setUp()
yang mencipta persediaan yang diperlukan yang setiap ujian perlu berfungsi dengan betul.
Untuk ujian unit ini, anda ingin menyemak dengan sampel kecil HTML halaman. Anda juga memerlukan akses kepada labah-labah buku anda dan beberapa kelas yang digunakan oleh rangka kerja secara dalaman untuk mengendalikan permintaan dan respons. Memandangkan anda memerlukan persediaan ini untuk setiap ujian, ini adalah calon yang baik untuk beralih ke kaedah .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"
)
# ...
Kaedah .setUp()
memulakan contoh BookSpider
sebelum setiap ujian, mencipta rentetan HTML contoh, yang mengandungi dua entri buku dan pautan ke halaman seterusnya. Ia kemudian mengejek permintaan ke tapak dengan mengembalikan objek HtmlResponse
dengan sampel HTML itu. Anda mahu HTML contoh sedekat mungkin dengan HTML halaman sebenar, tanpa membebankan pangkalan kod anda.
Anda boleh mengambil contoh HTML yang digunakan oleh tutorial ini daripada bahan yang boleh dimuat turun dalam books/tests/sample.html
:
Petikan dari halaman pendaratan Books to Scrape ini mengandungi dua elemen buku penuh dan pautan penomboran, tetapi sebaliknya dilucutkan supaya rentetan tidak menjadi terlalu panjang. Tambahkan coretan HTML ini di antara tanda petikan tiga kali ganda untuk menetapkannya kepada self.example_html
.
Nota: Jika anda ingin menggunakan HTML halaman penuh, maka anda juga boleh mengambilnya dengan penyemak imbas anda, permintaan
atau Scrapy dan menyimpannya sebagai pembolehubah rentetan. Hasilnya sepatutnya sama.
Walau bagaimanapun, perlu diingat bahawa jika anda mengambil kandungan setiap kali skrip ujian anda dijalankan, ia menimbulkan penalti prestasi dan hanya akan berfungsi jika anda disambungkan ke Internet.
Dengan persediaan anda disediakan, anda boleh membuat ujian pertama anda gagal:
# ...
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)
Jika anda menjalankan kod ujian anda pada ketika ini, anda akan mendapat satu ujian gagal dan dua ujian lulus. Navigasi ke direktori books/
peringkat atas anda, jika anda belum berada di sana, kemudian jalankan perintah 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)
Ujian kosong lulus kerana ia belum mengandungi sebarang penegasan lagi. .test_parse_scrapes_all_items()
anda gagal kerana anda belum lagi memanggil kaedah .parse()
dan mengendalikan nilai pulangannya. Lompat semula ke dalam kod dan tambahkan bahagian yang hilang untuk membuat ujian anda lulus:
# ...
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)
Anda mulakan dengan menambah import untuk Request
daripada scrapy.http
. Anda menggunakan kelas ini untuk membandingkan sama ada permintaan penomboran yang perlu dijana oleh rangka kerja adalah sebahagian daripada keputusan. Anda juga ingin mengesahkan bahawa Scrapy mengumpulkan maklumat buku dalam BooksItem
, jadi anda mengimportnya daripada books.items
.
Perlu diingat bahawa .parse()
ialah penjana. Untuk kemudahan, anda mengumpulkan semua item yang ia hasilkan ke dalam senarai. Kemudian, anda menambah dua pemahaman senarai yang menghimpun semua objek dikt
dan Minta
ke dalam dua senarai yang anda buat sebelum ini semasa menyediakan garis besar kaedah ujian ini. HTML sampel mengandungi dua item buku dan satu URL penomboran, jadi itulah perkara yang anda semak dalam pernyataan anda.
Nota: Jika anda menggunakan HTML halaman penuh untuk ujian anda, maka anda harus menerima dua puluh item buku dan masih hanya satu permintaan penomboran. Anda perlu menyesuaikan pernyataan anda dengan sewajarnya untuk lulus ujian.
Anda kini boleh menjalankan unittest
pada masa yang lain dan ketiga-tiga ujian harus lulus:
(venv) $ python -m unittest
...
----------------------------------------------------------------------
Ran 3 tests in 0.011s
Anda boleh terus membangunkan dua kaedah ujian lain menggunakan pendekatan yang serupa. Dalam bahagian boleh lipat di bawah, anda boleh menemui contoh pelaksanaan:
Anda boleh menyalin kod di bawah dan menampalnya ke dalam fail test_book.py
anda:
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()
Harap maklum bahawa anda masih perlu mengemas kini nilai untuk .example_html
dengan contoh HTML yang boleh anda temui dalam books/tests/sample.html
dalam sumber yang boleh dimuat turun:
Persediaan ini memastikan bahawa komponen individu BookSpider
diuji untuk ketepatannya, membantu mengenal pasti dan membetulkan isu awal dalam proses pembangunan.
Tangani Cabaran Mengikis Web Biasa
Mengikis web boleh menjadi alat yang berkuasa, tetapi selalunya ia tidak semudah yang anda harapkan. Sama ada anda berurusan dengan kandungan yang dijana secara dinamik atau perlu menavigasi sekitar mekanisme anti-mengikis, bersedia untuk menangani halangan ini adalah penting untuk membina pengikis web yang mantap dan boleh dipercayai.
Dalam bahagian ini, anda akan meneroka beberapa cabaran paling biasa yang mungkin anda hadapi. Anda juga akan mengenali beberapa penyelesaian praktikal untuk mengatasinya menggunakan Scrapy dan ekosistemnya yang luas.
Dilengkapi dengan alatan dan amalan terbaik ini, anda akan lebih bersedia untuk membina pengikis berdaya tahan yang mampu mengekstrak data berharga juga daripada tapak web yang lebih mencabar.
Cuba Semula Permintaan Gagal
Pengikisan web selalunya melibatkan penghantaran banyak permintaan ke pelayan web. Sesetengah permintaan ini mungkin mengakibatkan ralat sambungan disebabkan oleh isu rangkaian, masa henti pelayan atau sekatan sementara daripada tapak sasaran. Ralat ini boleh mengganggu proses mengikis dan membawa kepada pengumpulan data yang tidak lengkap. Melaksanakan logik cuba semula yang mantap dalam projek anda membantu memastikan isu sementara tidak menyebabkan labah-labah anda gagal.
Scrapy menyediakan sokongan terbina dalam untuk mencuba semula permintaan yang menghadapi jenis ralat tertentu. RetryMiddleware
ini didayakan secara lalai, jadi untuk kebanyakan kes anda tidak perlu melakukan apa-apa dan Scrapy akan mencuba semula permintaan biasa yang gagal untuk anda secara automatik. Percubaan semula middleware yang gagal meminta beberapa kali tertentu sebelum menyerah, meningkatkan peluang pengekstrakan data yang berjaya.
Jika anda perlu menyesuaikan tingkah laku RetryMiddleware
, maka anda boleh melakukannya dengan mudah terus dalam fail settings.py
anda. Daripada pilihan konfigurasi yang tersedia, terdapat tiga yang anda mungkin mahu laraskan untuk projek anda:
RETRY_ENABLED
: Mendayakan atau melumpuhkan perisian tengah cuba semula. Ia didayakan secara lalai.RETRY_TIMES
: Menentukan bilangan maksimum percubaan semula untuk permintaan yang gagal. Secara lalai, rangka kerja mencuba semula permintaan yang gagal dua kali.RETRY_HTTP_CODES
: Mentakrifkan kod respons HTTP yang sepatutnya mencetuskan percubaan semula.
Seperti yang dinyatakan, Scrapy mencuba semula permintaan anda secara lalai, tetapi jika anda perlu mengkonfigurasi perisian tengah cuba semula untuk projek Scrapy anda di luar lalai, maka anda boleh mengubah suai nilai ini dalam fail settings.py
:
# ...
RETRY_TIMES = 3
RETRY_HTTP_CODES = [500, 429]
Dalam contoh ini, anda menukar bilangan percubaan semula setiap permintaan kepada 3
dan menyesuaikan kod HTTP lalai supaya Scrapy hanya mencuba semula permintaan apabila ia menerima HTTP 500
(Ralat Pelayan Dalaman) dan kod ralat 429
(Terlalu Banyak Permintaan). Walau bagaimanapun, kekal dengan tetapan lalai yang Scrapy laksanakan biasanya merupakan idea yang baik untuk kebanyakan projek pengikis.
Ringkasnya, Scrapy mengendalikan logik cuba semula untuk anda secara lalai melalui RetryMiddleware
terbina dalamnya, yang juga boleh anda sesuaikan. Kerana itu, anda boleh mencapai pengekstrakan data yang lebih dipercayai dan mengurangkan kesan masalah sementara.
Berurusan Dengan Kandungan Dinamik
Mengikis kandungan dinamik, seperti halaman yang diberikan oleh JavaScript, memberikan satu set cabaran yang unik. Teknik mengikis web tradisional yang bergantung pada HTML statik tidak berfungsi apabila anda berurusan dengan kandungan yang dijana secara dinamik. Terdapat beberapa pendekatan yang boleh anda ambil untuk masih mendapatkan akses kepada data yang anda minati:
Menghasilkan semula permintaan: Selalunya, penyemak imbas anda mengambil data yang anda perlukan melalui panggilan API di latar belakang. Dengan memeriksa permintaan rangkaian yang dibuat oleh penyemak imbas menggunakan alat pembangun dalam penyemak imbas anda, anda boleh mengenal pasti titik akhir API ini dan terus membuat permintaan serupa daripada pengikis anda. Ini memintas keperluan untuk pemaparan JavaScript dan lebih cekap.
Gunakan Splash untuk pra-memaparkan JavaScript: Splash ialah penyemak imbas tanpa kepala yang direka khusus untuk mengikis web. Ia membolehkan anda membuat JavaScript dan menangkap HTML yang terhasil. Scrapy mempunyai middleware khusus untuk Splash, dipanggil scrapy-splash, yang membolehkan anda menyepadukannya dengan lancar dalam projek anda.
Automasikan penyemak imbas: Alat seperti Selenium dan Penulis Drama menyediakan automasi penyemak imbas penuh, termasuk pelaksanaan JavaScript. Selenium sudah mantap dan digunakan secara meluas, manakala Playwright menawarkan alternatif yang lebih baharu, lebih pantas dan lebih dipercayai dengan sokongan terbina dalam untuk berbilang penyemak imbas. Scrapy boleh disepadukan dengan Playwright menggunakan pakej
scrapy-playwright
.
Walaupun mengikis kandungan daripada halaman yang diberikan JavaScript sudah tentu meningkatkan kesukaran untuk projek mengikis web anda, Scrapy memberikan anda penyepaduan yang berguna yang akan membantu anda menyelesaikan kerja.
Uruskan Mekanisme Anti-Mengikis
Banyak tapak web menggunakan langkah anti-mengikis untuk melindungi data mereka dan memastikan penggunaan sumber mereka secara saksama. Anda boleh memintas beberapa langkah ini dengan tahap usaha yang berbeza-beza.
Scrapy membolehkan anda menyesuaikan beberapa pendekatan yang paling biasa digunakan secara langsung dalam fail settings.py
projek.
Tapak web sering menyekat permintaan daripada pelanggan tanpa pengepala ejen pengguna atau lalai. Menetapkan ejen pengguna tersuai dalam tetapan projek anda boleh membantu meniru penyemak imbas sebenar:
# ...
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"
Anda juga boleh menggunakan perisian tengah untuk memutar ejen dan proksi pengguna, yang boleh membantu mengedarkan permintaan dan mengurangkan peluang disekat:
# ...
DOWNLOADER_MIDDLEWARES = {
"scrapy.downloadermiddlewares.useragent.UserAgentMiddleware": None,
"scrapy_user_agents.middlewares.RandomUserAgentMiddleware": 400,
}
# ...
Akhir sekali, anda boleh memperkenalkan kelewatan antara permintaan, yang juga boleh membantu mengelak daripada mencetuskan langkah anti-bot:
# ...
DOWNLOAD_DELAY = 2
Jika tapak membina banyak pertahanan terhadap pengikis web, maka anda mungkin ingin mempertimbangkan sama ada ia benar-benar idea yang baik untuk mengikisnya. Untuk mana-mana tapak yang anda merancang untuk mengikis, anda hendaklah sentiasa menyemak dan mematuhi fail robots.txt
nya. Fail ini menentukan kebenaran tapak untuk perangkak web. Scrapy juga mempunyai tetapan untuk ini, yang didayakan secara lalai:
# ...
ROBOTSTXT_OBEY = True
# ...
Dengan menggunakan teknik dan konfigurasi ini, anda boleh menangani cabaran biasa dalam mengikis web, memastikan pengikis anda teguh, cekap dan menghormati tapak web yang anda berinteraksi.
Kesimpulan
Dengan mengikuti tutorial ini, anda telah berjaya menggunakan Scrapy dan MongoDB untuk projek mengikis web lengkap yang mengikuti proses ETL. kerja bagus!
Anda telah mempelajari tentang pelbagai jenis fungsi Scrapy, termasuk keupayaan ujian yang dihantar dengannya, dan cara anda boleh melanjutkan di atasnya. Anda juga telah mendapat cerapan tentang cara menangani cabaran pengikisan web biasa, seperti mengurus percubaan semula, menangani kandungan dinamik dan memintas mekanisme anti-mengikis.
Dalam tutorial ini, anda mempelajari cara untuk:
- Sediakan dan konfigurasikan Projek Scrapy
- Bina pengikis web berfungsi dengan Scrapy
- Ekstrak data daripada tapak web menggunakan pemilih
- Simpan data yang dikikis dalam pangkalan data MongoDB
- Uji dan nyahpepijat pengikis web Scrapy anda
Dengan kemahiran ini, anda dilengkapi dengan baik untuk menangani pelbagai projek mengikis web, memanfaatkan kecekapan Scrapy dan fleksibiliti MongoDB. Sama ada anda mengumpul data untuk penyelidikan, membina aplikasi dipacu data atau hanya meneroka web, pengetahuan dan alatan yang anda peroleh daripada tutorial ini akan membantu anda dengan baik. Teruskan mengikis dan tetap hormat!