Apa praktik terbaik untuk menulis web scraper yang dapat dipelihara?

Saya perlu menerapkan beberapa pencakar untuk merayapi beberapa halaman web (karena situs tersebut tidak memiliki API terbuka), mengekstrak informasi dan menyimpannya ke database. Saat ini saya menggunakan sup yang indah untuk menulis kode seperti ini:

discount_price_text = soup.select("#detail-main del.originPrice")[0].string;
discount_price = float(re.findall('[\d\.]+', discount_price_text)[0]);

Saya kira kode seperti ini dapat dengan mudah menjadi tidak valid ketika halaman web diubah, meskipun sedikit. Bagaimana cara saya menulis scraper yang tidak terlalu rentan terhadap perubahan ini, selain menulis tes regresi agar dijalankan secara teratur untuk mendeteksi kegagalan?

Secara khusus, apakah ada 'scraper pintar' yang dapat membuat 'usaha terbaik menebak' bahkan ketika pemilih xpath/css asli tidak lagi valid?


person NeoWang    schedule 21.01.2014    source sumber
comment
Selenium. pypi.python.org/pypi/selenium   -  person Priyank Patel    schedule 21.01.2014


Jawaban (3)


Halaman mempunyai potensi untuk berubah secara drastis sehingga membuat scraper yang sangat "pintar" mungkin cukup sulit; dan jika memungkinkan, scrapernya tidak dapat diprediksi, bahkan dengan teknik canggih seperti pembelajaran mesin dan sebagainya. Sulit untuk membuat scraper yang dapat dipercaya dan memiliki fleksibilitas otomatis.

Pemeliharaan adalah sebuah bentuk seni yang berpusat pada bagaimana penyeleksi didefinisikan dan digunakan.

Di masa lalu saya telah menggulirkan penyeleksi "dua tahap" saya sendiri:

  1. (temukan) Tahap pertama sangat tidak fleksibel dan memeriksa struktur halaman menuju elemen yang diinginkan. Jika tahap pertama gagal, maka akan muncul semacam kesalahan "struktur halaman berubah".

  2. (mengambil) Tahap kedua kemudian agak fleksibel dan mengekstrak data dari elemen yang diinginkan pada halaman.

Hal ini memungkinkan scraper mengisolasi dirinya dari perubahan halaman drastis dengan tingkat deteksi otomatis tertentu, sambil tetap mempertahankan tingkat fleksibilitas yang dapat dipercaya.

Saya sering menggunakan penyeleksi xpath, dan ini sungguh mengejutkan, dengan sedikit latihan, betapa fleksibelnya Anda dengan pemilih yang baik namun tetap sangat akurat. Saya yakin penyeleksi css serupa. Ini menjadi lebih mudah jika desain halamannya semakin semantik dan "datar".

Beberapa pertanyaan penting yang harus dijawab adalah:

  1. Apa yang Anda harapkan akan berubah pada halaman ini?

  2. Apa yang Anda harapkan tetap sama di halaman ini?

Saat menjawab pertanyaan-pertanyaan ini, semakin akurat Anda, semakin baik pula penyeleksi Anda.

Pada akhirnya, terserah Anda seberapa besar risiko yang ingin Anda ambil, seberapa dapat dipercaya penyeleksi Anda, kapan menemukan dan mengambil data pada halaman, cara Anda menyusunnya akan membuat perbedaan besar; dan idealnya, yang terbaik adalah mendapatkan data dari web-api, yang diharapkan akan tersedia lebih banyak sumber.


EDIT: Contoh kecil

Menggunakan skenario Anda, di mana elemen yang Anda inginkan berada di .content > .deal > .tag > .price, pemilih .content .price umum sangat "fleksibel" mengenai perubahan halaman; tetapi jika, misalnya, muncul elemen positif palsu, kita mungkin ingin menghindari mengekstraksi elemen baru ini.

Dengan menggunakan penyeleksi dua tahap, kita dapat menentukan tahap pertama yang kurang umum dan lebih tidak fleksibel seperti .content > .deal, dan kemudian tahap kedua yang lebih umum seperti .price untuk mengambil elemen terakhir menggunakan kueri relatif terhadap hasil Pertama.

Jadi mengapa tidak menggunakan pemilih seperti .content > .deal .price saja?

Untuk penggunaan saya, saya ingin dapat mendeteksi perubahan halaman yang besar tanpa menjalankan tes regresi tambahan secara terpisah. Saya menyadari bahwa daripada satu pemilih besar, saya bisa menulis tahap pertama untuk memasukkan elemen struktur halaman yang penting. Tahap pertama ini akan gagal (atau dilaporkan) jika elemen strukturalnya tidak ada lagi. Kemudian saya bisa menulis tahap kedua untuk mengambil data dengan lebih baik dibandingkan dengan hasil tahap pertama.

Saya tidak boleh mengatakan bahwa ini adalah praktik "terbaik", namun telah berhasil dengan baik.

person David    schedule 23.01.2014
comment
Terima kasih! Saya sangat setuju memilih penyeleksi yang kuat adalah sebuah bentuk seni. Saya sebenarnya berpikir untuk menulis beberapa level penyeleksi dari yang sangat spesifik (seperti .content›.deal›.tag›.price) hingga yang sangat umum seperti (.content .price), dan kembali ke level berikutnya jika level saat ini gagal, tapi saya tidak yakin itu ide yang bagus, karena bisa menimbulkan hasil positif palsu. Terkadang lebih baik gagal daripada mendapatkan data yang salah... Dan dalam model 2 tahap Anda, apa yang Anda maksud ketika Anda mengatakan bahwa pengambilan bisa dilakukan secara fleksibel'? Ketika saya menemukan elemennya, saya hanya perlu mengekstrak datanya, bukan? - person NeoWang; 24.01.2014
comment
Yang saya maksud dengan agak fleksibel adalah relatif fleksibel terhadap sub-bagian halaman yang diambil oleh pemilih tahap pertama. Saya telah menambahkan contoh kecil di atas. - person David; 24.01.2014

Sama sekali tidak terkait dengan Python dan tidak fleksibel secara otomatis, namun menurut saya templat Xidel scraper saya memiliki pemeliharaan terbaik.

Anda akan menulisnya seperti:

<div id="detail-main"> 
   <del class="originPrice">
     {extract(., "[0-9.]+")} 
   </del>
</div>

Setiap elemen templat dicocokkan dengan elemen di halaman web, dan jika elemen tersebut sama, ekspresi di dalam {} akan dievaluasi.

Elemen tambahan pada halaman akan diabaikan, jadi jika Anda menemukan keseimbangan yang tepat antara elemen yang disertakan dan elemen yang dihapus, template tidak akan terpengaruh oleh semua perubahan kecil. Perubahan besar di sisi lain akan memicu kegagalan pencocokan, jauh lebih baik daripada xpath/css yang hanya akan mengembalikan set kosong. Kemudian Anda dapat mengubah di template hanya elemen yang diubah, idealnya Anda dapat langsung menerapkan perbedaan antara halaman lama/yang diubah ke template. Dalam kasus apa pun, Anda tidak perlu mencari pemilih mana yang terpengaruh atau memperbarui beberapa pemilih untuk satu perubahan, karena templat dapat memuat semua kueri untuk satu halaman secara bersamaan.

person BeniBela    schedule 23.01.2014

EDIT: Ups, sekarang saya melihat Anda sudah menggunakan pemilih CSS. Saya pikir mereka memberikan jawaban terbaik untuk pertanyaan Anda. Jadi tidak, menurut saya tidak ada cara yang lebih baik.

Namun, terkadang Anda mungkin merasa lebih mudah mengidentifikasi data tanpa struktur. Misalnya, jika Anda ingin mengurangi harga, Anda dapat melakukan penelusuran ekspresi reguler yang cocok dengan harga (\$\s+[0-9.]+), alih-alih mengandalkan struktur.


Secara pribadi, perpustakaan webscraping out-of-the-box yang telah saya coba semuanya meninggalkan sesuatu yang diinginkan (mekanisasi, Scrapy, dan lainnya).

Saya biasanya menggulung sendiri, menggunakan:

cssselect memungkinkan Anda menggunakan pemilih CSS (seperti jQuery) untuk menemukan div, tabel tertentu, dan sebagainya. Ini terbukti sangat berharga.

Contoh kode untuk mengambil pertanyaan pertama dari beranda SO:

import urllib2
import urlparse
import cookielib

from lxml import etree
from lxml.cssselect import CSSSelector

post_data = None
url = 'http://www.stackoverflow.com'
cookie_jar = cookielib.CookieJar()
http_opener = urllib2.build_opener(
    urllib2.HTTPCookieProcessor(cookie_jar),
    urllib2.HTTPSHandler(debuglevel=0),
)
http_opener.addheaders = [
    ('User-Agent', 'Mozilla/5.0 (X11; Linux i686; rv:25.0) Gecko/20100101 Firefox/25.0'),
    ('Accept', 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'),
]
fp = http_opener.open(url, post_data)
parser = etree.HTMLParser()
doc = etree.parse(fp, parser)

elem = CSSSelector('#question-mini-list > div:first-child > div.summary h3 a')(doc)
print elem[0].text

Tentu saja Anda tidak memerlukan cookiejar, atau agen pengguna untuk meniru FireFox, namun saya merasa sering membutuhkannya saat menggores situs.

person Community    schedule 22.01.2014