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.

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.

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:

  1. Tajuk
  2. harga
  3. 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.

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.

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.

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 and a

Menyasarkan elemen jenis elemen HTML tersebut

>

Menunjukkan elemen kanak-kanak

.price_color and article.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.

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:

  1. Di mana untuk mula mengikis
  2. 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.

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.

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 pengecualian DropItem.
  • .from_crawler() mencipta saluran paip daripada Crawler 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.

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 daripada Crawler 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 daripada settings.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.

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 membawa DropItem daripada scrapy.exceptions, yang merupakan cara cepat untuk menggugurkan item pendua dan menulis ke log Scrapy.

  • Barisan 28 memanggil .compute_item_id() dan memberikan output cincang kepada item_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 pengecualian DropItem, 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 menambah item_id yang dikira sebagai nilai untuk atribut ._id pada Item 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:

  1. Labah-labah anda menambah nilai untuk .url, .title dan .price pada BooksItem.
  2. 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:

  1. penapis untuk mencari dokumen dalam koleksi anda
  2. 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 dan DropItem eksplisit, maka anda akan terharu dengan log AMARAN. Scrapy secara automatik menulis AMARAN apabila ia menangkap pengecualian DropItem.

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():

  1. @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.
  2. @returns item 20 20 menyatakan bahawa kaedah harus mengembalikan tepat dua puluh item. Nombor pertama ialah item jangkaan minimum, dan yang kedua ialah maksimum.
  3. @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.
  4. @scrapes url title price menyatakan bahawa setiap item yang dikembalikan harus mengandungi medan url, title dan price.

Dengan hanya empat baris ini, anda telah menyediakan ujian asas untuk .parse() yang boleh membantu anda mengesan masalah dengan pengikis anda lebih awal.

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.

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.

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.

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.txtnya. 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!