Pengikisan web di R, Bagian 2. Mempercepat proses dengan komputasi paralel dan menggunakan paket Rcrawler


Dalam artikel sebelumnya , menggunakan parsing scrapbooking, saya mengumpulkan peringkat film dari situs IMDB dan Kinopoisk dan membandingkannya. Repositori di Github .


Kode melakukan tugasnya dengan baik, tetapi memo sering digunakan untuk "mengikis" bukan beberapa halaman, tetapi beberapa tiga ribu, dan kode dari artikel sebelumnya tidak cocok untuk goresan "besar" seperti itu. Lebih tepatnya, itu tidak akan optimal. Pada prinsipnya, praktis tidak ada yang mencegah Anda menggunakannya untuk merayapi ribuan halaman. Praktis, karena Anda tidak punya banyak waktu



Ketika saya memutuskan untuk menggunakan scraping_imdb.R untuk menjelajah 1000 halaman


Optimasi kode. Satu penggunaan fungsi read_html

Dalam artikel ini, 100 tautan ke halaman-halaman toko buku Labyrinth akan digunakan untuk memeriksa operasi dan kecepatan kode.


Perubahan eksplisit yang dapat mempercepat proses adalah penggunaan fungsi kode "paling lambat" - read_html . Biarkan saya mengingatkan Anda bahwa dia "membaca" halaman HTML. Dalam versi pertama kode untuk situs film, saya menjalankan read_html setiap kali saya perlu mendapatkan nilai (nama film, tahun, genre, peringkat). Sekarang jejak "rasa malu" ini telah dihapus dari GitHuba, tetapi ternyata sudah. Tidak ada artinya dalam hal ini, karena variabel yang dibuat menggunakan read_html berisi informasi tentang seluruh halaman dan untuk mendapatkan data yang berbeda dari itu, cukup untuk html_nodes variabel ini ke fungsi html_nodes dan tidak mulai membaca HTML setiap saat. Sehingga Anda dapat menghemat waktu secara proporsional dengan jumlah nilai yang ingin Anda dapatkan. Dari Labyrinth, saya mendapatkan tujuh nilai, masing-masing, kode yang hanya menggunakan satu pembacaan halaman HTML akan bekerja sekitar tujuh kali lebih cepat. Tidak buruk! Tetapi sebelum saya "mempercepat" lagi, saya akan ngelantur dan berbicara tentang hal-hal menarik yang muncul ketika mengikis dari situs web Labyrinth.


Fitur pengikisan halaman di Labirin

Pada bagian ini, saya tidak akan menyentuh prosedur untuk mendapatkan dan menghapus data yang disebutkan dalam artikel sebelumnya. Saya hanya akan menyebutkan momen-momen yang pertama kali saya temui ketika menulis kode untuk scrapbooking toko buku.


Pertama, perlu disebutkan strukturnya. Dia sangat tidak nyaman. Sebaliknya, misalnya, dari situs web Read-Cities, bagian dari genre dengan "filter kosong" hanya memberikan 17 halaman. Tentu saja, semua 8011 buku dalam genre "Prosa Asing Kontemporer" tidak cocok untuk mereka.


Oleh karena itu, saya tidak menemukan sesuatu yang lebih baik daripada berkeliling di https://www.labirint.ru/books/ **** tautan dengan patung sederhana. Terus terang, metode ini bukan yang terbaik (jika hanya karena sebagian besar buku "kuno" tidak memiliki informasi kecuali nama dan karena itu praktis tidak berguna), jadi jika ada yang menawarkan solusi yang lebih elegan, saya akan senang. Tetapi saya menemukan bahwa di bawah angka pertama yang bangga di situs web Labyrinth adalah sebuah buku berjudul "Cara Membuat Moonshine" . Sayangnya, sudah tidak mungkin untuk membeli gudang pengetahuan ini.


Semua alamat selama pencacahan dapat dibagi menjadi dua jenis:


  • Halaman yang ada
  • Halaman yang tidak ada

Halaman yang ada, pada gilirannya, dapat dibagi menjadi dua bagian:


  • Halaman yang berisi semua informasi yang diperlukan
  • Halaman yang tidak mengandung semua informasi yang diperlukan

Saya berakhir dengan tabel data dengan tujuh kolom:


  1. ISBN - Nomor Buku ISBN
  2. PRICE - harga buku
  3. NAME - judul buku
  4. PENULIS - penulis buku
  5. PUBLISHER - penerbit
  6. TAHUN - tahun publikasi
  7. HALAMAN - jumlah halaman

Semuanya jelas dengan halaman dengan informasi lengkap, mereka tidak memerlukan perubahan apa pun dibandingkan dengan kode untuk situs film.


Adapun halaman di mana beberapa data tidak tersedia, tidak begitu sederhana dengan mereka. Pencarian di halaman hanya akan mengembalikan nilai-nilai yang ditemukan dan panjang output akan berkurang dengan jumlah elemen yang tidak akan ditemukan. Ini akan menghancurkan seluruh struktur. Untuk menghindari ini, konstruksi if ... else ditambahkan ke setiap argumen, yang memperkirakan panjang vektor yang diperoleh setelah menggunakan fungsi html_nodes dan jika itu nol, ia mengembalikan NA untuk menghindari nilai bias.


  PUBLISHER <- unlist(lapply(list_html, function(n){ publishing <- if(n != "NA") { publishing_html <- html_nodes(n, ".publisher a") publishing <- if(length(publishing_html) == 0){ NA } else { publishing <- html_text(publishing_html) } } else { NA } })) 

Tapi seperti yang Anda bisa perhatikan di sini sebanyak dua jika dan sebanyak dua lainnya. Hanya "internal" if..esle yang relevan dengan solusi untuk masalah yang dijelaskan di atas. Eksterior menyelesaikan masalah dengan halaman yang tidak ada.


Halaman yang tidak memiliki masalah besar. Jika nilai digeser pada halaman dengan data yang hilang, maka ketika input read_html halaman yang tidak ada, fungsi akan read_html kesalahan dan kode akan berhenti dieksekusi. Karena entah bagaimana tidak mungkin mendeteksi halaman seperti itu di muka, perlu untuk memastikan bahwa kesalahan tidak menghentikan seluruh proses.


Fungsi possibly dari paket yang possibly akan membantu kami dengan ini. Arti fungsi possibly (selain possibly quietly dan safely ) adalah untuk mengganti hasil cetak efek samping (misalnya, kesalahan) dengan nilai yang sesuai dengan kita. possibly memiliki struktur yang possibly(.f, otherwise) dan jika kesalahan terjadi dalam kode, alih-alih menghentikan eksekusi, ia menggunakan nilai default (jika tidak). Dalam kasus kami, tampilannya seperti ini:


 book_html <- possibly(read_html, "NA")(n) 

n adalah daftar alamat halaman situs yang kami memo. Pada output, kita mendapatkan daftar panjang n, di mana elemen dari halaman yang ada akan berada dalam bentuk "normal" untuk melakukan fungsi read_html , dan elemen dari halaman yang tidak ada akan terdiri dari vektor karakter "NA". Harap perhatikan bahwa nilai default harus berupa vektor karakter, karena di masa mendatang kami akan merujuknya. Jika kita hanya menulis NA , seperti pada bagian kode PUBLISHER, ini tidak akan mungkin. Untuk menghindari kebingungan, Anda dapat mengubah nilai sebaliknya dari NA ke yang lain.


Dan sekarang kembali ke kode untuk mendapatkan nama penerbit. Eksternal jika ... diperlukan untuk tujuan yang sama seperti internal, tetapi sehubungan dengan halaman yang tidak ada. Jika variabel book_html sama dengan "NA", maka masing-masing nilai "scraped" juga sama dengan NA (di sini Anda sudah bisa menggunakan NA "asli", daripada penipu simbolis). Jadi pada akhirnya, kita mendapatkan tabel dari formulir berikut:


ISBNHARGANAMEPENULISPENERBITTahunHalaman
46653057703221488Set String Art "Cute Puppy" (30 * 30 cm) (DH6021)NAKucing jahe2019NA
NANANANANANANA
9785171160814273Arkady Averchenko: Cerita menyenangkan untuk anak-anakPenulis: Averchenko Arkady Timofeevich, Artis: Vlasova Anna YulievnaNak2019288

Sekarang kembali dengan percepatan proses pengikisan.


Komputasi paralel dalam perbandingan R. Kecepatan dan jebakan saat menggunakan fungsi read_html

Secara default, semua perhitungan dalam R dilakukan pada inti prosesor yang sama. Dan sementara inti yang malang ini berkeringat, "mengorek" data dari ribuan halaman untuk kami, kawan-kawan kami yang lain "mendinginkan" dengan melakukan beberapa tugas lain. Menggunakan komputasi paralel membantu menarik semua inti prosesor untuk memproses / menerima data, yang mempercepat proses.


Saya tidak akan masuk jauh ke dalam desain komputasi paralel pada R, Anda dapat membaca lebih lanjut tentang mereka, misalnya di sini . Cara saya memahami paralelisme pada R adalah membuat salinan R dalam kelompok terpisah sesuai dengan jumlah kernel yang ditunjukkan yang berinteraksi satu sama lain melalui soket .


Saya akan memberi tahu Anda tentang kesalahan yang saya buat saat menggunakan komputasi paralel. Awalnya, rencana saya adalah ini: menggunakan komputasi paralel, saya mendapatkan daftar 100 halaman "baca" read_html , dan kemudian dalam mode normal saya hanya mendapatkan data yang saya butuhkan. Pada awalnya semuanya berjalan dengan baik: Saya mendapat daftar, menghabiskan waktu jauh lebih sedikit daripada dalam mode normal R. Tapi hanya ketika saya mencoba berinteraksi dengan daftar ini, saya menerima kesalahan:


 Error: external pointer is not valid 

Akibatnya, saya menyadari apa masalahnya, melihat contoh-contoh di Internet, dan setelah itu, menurut hukum kekejaman, saya menemukan penjelasan Henrik Bengtsson dalam sketsa untuk paket mendatang . Faktanya adalah bahwa fungsi XML dari paket xml2 adalah objek yang tidak dapat diekspor.
) Objek-objek ini "terikat" ke sesi R ini dan tidak dapat ditransfer ke proses lain, yang saya coba lakukan. Oleh karena itu, fungsi yang diluncurkan dalam komputasi paralel harus berisi "siklus penuh" operasi: membaca halaman HTML, menerima dan membersihkan data yang diperlukan.


Membuat komputasi paralel itu sendiri tidak membutuhkan banyak waktu dan garis kode. Hal pertama yang Anda butuhkan adalah mengunduh perpustakaan. Repositori Github menunjukkan paket mana yang diperlukan untuk metode mana. Di sini saya akan menunjukkan komputasi paralel menggunakan fungsi parLapply dari paket parallel . Untuk melakukan ini, jalankan saja doParallel ( parallel akan mulai secara otomatis dalam kasus ini). Jika Anda tiba-tiba tidak tahu atau lupa jumlah inti prosesor Anda, deteksi berapa banyak dari mereka yang akan membantu mendeteksi detectCores


 # detectCores - ,     number_cl <- detectCores() 

Selanjutnya, buat salinan paralel R:


  # makePSOCKcluster -    R,    cluster <- makePSOCKcluster(number_cl) registerDoParallel(cluster) 

Sekarang kita sedang menulis fungsi yang akan melakukan semua prosedur yang kita butuhkan. Saya perhatikan sejak itu sesi baru dibuat. Paket R yang fungsinya digunakan dalam fungsi kita sendiri harus dituliskan di badan fungsi. Di spider_parallel.R, ini menyebabkan paket stringr berjalan dua kali: pertama untuk mendapatkan alamat halaman, dan kemudian menghapus data.


Dan kemudian prosedurnya hampir tidak berbeda dengan menggunakan fungsi lapply biasa. Di parLapply kami memberikan daftar alamat, fungsi kami sendiri, dan, satu-satunya tambahan, variabel dengan kluster yang kami buat.


 # parLapply -  lapply     big_list <- parLapply(cluster, list_url, scraping_parellel_func) #    stopCluster(cluster) 

Itu saja, sekarang tinggal membandingkan waktu yang dihabiskan.


Perbandingan kecepatan komputasi serial dan paralel

Ini akan menjadi titik terpendek. Komputasi paralel 5 kali lebih cepat dari biasanya:


Menggores kecepatan tanpa menggunakan komputasi paralel


penggunasistemberlalu
13.570,40112.84

Kecepatan Scraping Menggunakan Komputasi Paralel


penggunasistemberlalu
0,140,0512/21

Apa yang harus saya katakan? Komputasi paralel dapat menghemat banyak waktu Anda tanpa membuat kesulitan dalam membuat kode. Dengan peningkatan jumlah inti, kecepatan akan meningkat secara proporsional dengan jumlah mereka. Jadi, dengan beberapa perubahan, kami mempercepat kode 7 kali pertama (berhenti menghitung read_html di setiap langkah), dan kemudian 5 lagi, menggunakan perhitungan paralel. Skrip laba-laba tanpa komputasi paralel, menggunakan paket parallel dan foreach , ada di repositori di Github.


Gambaran kecil dari paket Rcrawler . Perbandingan kecepatan.

Ada beberapa cara lain untuk memo halaman HTML di R, tapi saya akan fokus pada paket Rcrawler . Fitur yang membedakannya dari alat lain dalam bahasa R adalah kemampuan untuk menjelajah situs. Anda dapat mengatur fungsi Rcrawler dengan nama yang sama ke alamat Rcrawler dan secara metodis, halaman per halaman, melewati seluruh situs. Rcrawler memiliki banyak argumen untuk mengatur pencarian (misalnya, Anda dapat mencari berdasarkan kata kunci, sektor situs (berguna ketika situs terdiri dari sejumlah besar halaman), kedalaman pencarian, mengabaikan parameter URL yang membuat halaman duplikat, dan banyak lagi. Juga dalam hal ini fungsi-fungsi telah diletakkan perhitungan paralel, yang ditentukan oleh argumen no_cores (jumlah core prosesor yang terlibat) dan no_conn (jumlah permintaan paralel).


Untuk kasus kami, memotong dari alamat yang ditentukan, ada fungsi ContentScraper . Ini tidak menggunakan komputasi paralel secara default, jadi Anda harus mengulang semua manipulasi yang saya jelaskan di atas. Saya menyukai fungsi itu sendiri - ia menyediakan banyak opsi untuk mengatur goresan dan dipahami dengan baik pada tingkat intuitif. Juga di sini Anda tidak dapat menggunakan if..else untuk halaman yang hilang atau nilai yang hilang, seperti eksekusi fungsi tidak berhenti.


 #   ContentScraper: # CssPatterns -    CSS    . # ExcludeCSSPat -    CSS ,    . # ,   CSS     CSS ,    . # ManyPerPattern -  FALSE,       , #  .  TRUE,     ,   . # PatternsName -      .   #   c  ,      t_func <- function(n){ library(Rcrawler) t <- ContentScraper(n, CssPatterns = c("#product-title", ".authors", ".buying-price-val-number", ".buying-pricenew-val-number", ".publisher", ".isbn", ".pages2"), ExcludeCSSPat = c(".prodtitle-availibility", ".js-open-block-page_count"), ManyPerPattern = FALSE, PatternsName = c("title", "author", "price1", "price2", "publisher", "isbn", "page")) return(t) } 

Tetapi dengan semua kualitas positif, fungsi ContentScraper memiliki satu minus sangat serius - kecepatan kerja.


Konten Rcrawler Rcrawler ContentScraper Tanpa Komputasi Paralel


penggunasistemberlalu
47.470,29212.24

Konten Rcrawler ContentScraper ContentScraper Rcrawler Menggunakan Parallel Computing


penggunasistemberlalu
0,010,0067,97

Jadi Rcrawler harus digunakan jika Anda perlu memotong situs tanpa terlebih dahulu menentukan alamat url, serta dengan sejumlah kecil halaman. Dalam kasus lain, kecepatan lambat akan melebihi semua kemungkinan keuntungan menggunakan paket ini.


Saya akan berterima kasih atas komentar, saran, keluhan
Tautan repositori Github
Profil Lingkaran Saya

Source: https://habr.com/ru/post/id464399/


All Articles