Nowoczesne drapanie stron internetowych za pomocą Pythona i Selenium
Opublikowany: 2022-03-11Web scraping był używany do wydobywania danych ze stron internetowych niemal od czasu narodzin sieci WWW. Na początku skrobanie odbywało się głównie na stronach statycznych – tych ze znanymi elementami, tagami i danymi.
Jednak ostatnio zaawansowane technologie w tworzeniu stron internetowych sprawiły, że zadanie to stało się nieco trudniejsze. W tym artykule przyjrzymy się, jak możemy zająć się skrobaniem danych w przypadku, gdy nowa technologia i inne czynniki uniemożliwiają standardowe skrobanie.
Tradycyjne pobieranie danych
Ponieważ większość witryn tworzy strony przeznaczone do czytelności dla człowieka, a nie do automatycznego czytania, skrobanie stron internetowych polegało głównie na programowym przetrawieniu danych dotyczących znaczników strony internetowej (pomyśl, że kliknij prawym przyciskiem myszy, Wyświetl źródło), a następnie wykryciu statycznych wzorców w tych danych, które umożliwiłyby programowi aby „odczytać” różne informacje i zapisać je w pliku lub bazie danych.
Jeśli dane raportu zostałyby znalezione, często dane byłyby dostępne przez przekazanie zmiennych formularza lub parametrów z adresem URL. Na przykład:
https://www.myreportdata.com?month=12&year=2004&clientid=24823
Python stał się jednym z najpopularniejszych języków web scrapingu, po części dzięki różnym bibliotekom internetowym, które zostały dla niego stworzone. Jedna popularna biblioteka, Beautiful Soup, jest przeznaczona do wyciągania danych z plików HTML i XML, umożliwiając wyszukiwanie, nawigację i modyfikowanie znaczników (tj. drzewa analizy).
Skrobanie w przeglądarce
Ostatnio miałem projekt dotyczący skrobania, który wydawał się całkiem prosty i byłem w pełni przygotowany do obsługi tradycyjnego skrobania. Ale gdy zagłębiałem się w to dalej, znalazłem przeszkody, których nie można było pokonać tradycyjnymi metodami.
Trzy główne problemy uniemożliwiły mi standardowe metody skrobania:
- Certyfikat. Aby uzyskać dostęp do części witryny, w której znajdowały się dane, wymagany był certyfikat. Podczas uzyskiwania dostępu do strony początkowej pojawił się monit z prośbą o wybranie właściwego certyfikatu z tych zainstalowanych na moim komputerze i kliknięcie OK.
- Ramki iframe. Witryna korzystała z ramek iframe, które zepsuły moje normalne skrobanie. Tak, mógłbym spróbować znaleźć wszystkie adresy URL iframe, a następnie utworzyć mapę witryny, ale wydawało się, że może to być nieporęczne.
- JavaScript. Dostęp do danych uzyskano po wypełnieniu formularza z parametrami (np. identyfikator klienta, zakres dat itp.). Normalnie pominąłbym formularz i po prostu przekazałbym zmienne formularza (za pośrednictwem adresu URL lub jako ukryte zmienne formularza) do strony wyników i zobaczenia wyników. Ale w tym przypadku formularz zawierał JavaScript, który nie pozwalał mi na dostęp do zmiennych formularza w normalny sposób.
Postanowiłem więc porzucić moje tradycyjne metody i przyjrzeć się możliwemu narzędziu do scrapingu w przeglądarce. To działałoby inaczej niż normalnie – zamiast bezpośrednio przechodzić do strony, pobierać drzewo analizy i wyciągać elementy danych, zamiast tego „zachowywałbym się jak człowiek” i korzystałbym z przeglądarki, aby dostać się do strony, której potrzebowałem, a następnie zeskrobać danych – w ten sposób omijając konieczność radzenia sobie z wymienionymi barierami.
Selen
Ogólnie rzecz biorąc, Selenium jest dobrze znane jako platforma testowa typu open source dla aplikacji internetowych – umożliwiając specjalistom ds. kontroli jakości przeprowadzanie zautomatyzowanych testów, wykonywanie odtworzeń i wdrażanie funkcji zdalnego sterowania (umożliwiając wiele instancji przeglądarki do testowania obciążenia i wiele typów przeglądarek). W moim przypadku wydawało się to przydatne.
Moim głównym językiem do web scrapingu jest Python, ponieważ ma dobrze zintegrowane biblioteki, które ogólnie mogą obsługiwać wszystkie wymagane funkcje. I rzeczywiście, istnieje biblioteka Selenium dla Pythona. To pozwoliłoby mi utworzyć instancję „przeglądarki” – Chrome, Firefox, IE itp. – a następnie udawać, że sam korzystam z przeglądarki, aby uzyskać dostęp do danych, których szukałem. A jeśli nie chciałem, aby przeglądarka faktycznie się pojawiała, mogłem stworzyć przeglądarkę w trybie „bezgłowym”, dzięki czemu nie będzie widoczna dla żadnego użytkownika.
Konfiguracja projektu
Aby zacząć eksperymentować, musiałem skonfigurować swój projekt i zdobyć wszystko, czego potrzebowałem. Użyłem komputera z systemem Windows 10 i upewniłem się, że mam stosunkowo zaktualizowaną wersję Pythona (była to wersja 3.7.3). Utworzyłem pusty skrypt Pythona, a następnie załadowałem biblioteki, które według mnie mogą być wymagane, używając PIP (instalatora pakietów dla Pythona), jeśli nie miałem jeszcze załadowanej biblioteki. Oto główne biblioteki, od których zacząłem:
- Żądania (do wykonywania żądań HTTP)
- URLLib3 (obsługa adresów URL)
- Piękna zupa (na wypadek, gdyby Selenium nie poradziła sobie ze wszystkim)
- Selenium (do nawigacji w przeglądarce)
Dodałem również kilka parametrów wywołania do skryptu (za pomocą biblioteki argparse), abym mógł bawić się różnymi zestawami danych, wywołując skrypt z wiersza poleceń z różnymi opcjami. Obejmowały one identyfikator klienta, od miesiąca/roku i do miesiąca/roku.
Problem 1 – Certyfikat
Pierwszym wyborem, którego musiałem dokonać, była przeglądarka, której miałem użyć Selenium. Ponieważ na ogół używam Chrome i jest on oparty na projekcie Chromium o otwartym kodzie źródłowym (używanym również przez przeglądarki Edge, Opera i Amazon Silk), pomyślałem, że spróbuję tego najpierw.
Udało mi się uruchomić Chrome w skrypcie, dodając potrzebne składniki biblioteki, a następnie wydając kilka prostych poleceń:
# Load selenium components from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait, Select from selenium.webdriver.support import expected_conditions as EC from selenium.common.exceptions import TimeoutException # Establish chrome driver and go to report site URL url = "https://reportdata.mytestsite.com/transactionSearch.jsp" driver = webdriver.Chrome() driver.get(url)
Ponieważ nie uruchomiłem przeglądarki w trybie bezgłowym, przeglądarka faktycznie się pojawiła i mogłem zobaczyć, co robi. Natychmiast poprosił mnie o wybranie certyfikatu (który wcześniej zainstalowałem).
Pierwszym problemem do rozwiązania był certyfikat. Jak wybrać właściwy i zaakceptować go, aby dostać się na stronę? W moim pierwszym teście skryptu otrzymałem następujący monit:
To nie było dobre. Nie chciałem ręcznie klikać przycisku OK za każdym razem, gdy uruchamiałem mój skrypt.
Jak się okazuje udało mi się znaleźć obejście tego - bez programowania. Chociaż miałem nadzieję, że Chrome ma możliwość przekazania nazwy certyfikatu podczas uruchamiania, ta funkcja nie istniała. Jednak Chrome ma możliwość automatycznego wyboru certyfikatu, jeśli w rejestrze systemu Windows istnieje określony wpis. Możesz ustawić go, aby wybrać pierwszy certyfikat, który widzi, lub być bardziej szczegółowy. Ponieważ miałem załadowany tylko jeden certyfikat, użyłem formatu ogólnego.
Tak więc przy tym zestawie, kiedy powiedziałem Selenium, aby uruchomił Chrome i pojawił się monit o certyfikat, Chrome „autowybrał” certyfikat i kontynuował.
Problem 2 – Ramki iframe
OK, więc teraz byłem na stronie i pojawił się formularz, prosząc mnie o wpisanie identyfikatora klienta i zakresu dat raportu.

Badając formularz w narzędziach programistycznych (F12), zauważyłem, że formularz jest prezentowany w ramce iframe. Tak więc, zanim mogłem zacząć wypełniać formularz, musiałem „przełączyć się” na właściwy iframe, w którym formularz istniał. Aby to zrobić, wywołałem funkcję przełączania na Selenium, na przykład:
# Switch to iframe where form is frame_ref = driver.find_elements_by_tag_name("iframe")[0] iframe = driver.switch_to.frame(frame_ref)
Dobrze, więc teraz w odpowiedniej ramce mogłem określić składniki, wypełnić pole ID klienta i wybrać listy rozwijane dat:
# Find the Customer ID field and populate it element = driver.find_element_by_name("custId") element.send_keys(custId) # send a test id # Find and select the date drop-downs select = Select(driver.find_element_by_name("fromMonth")) select.select_by_visible_text(from_month) select = Select(driver.find_element_by_name("fromYear")) select.select_by_visible_text(from_year) select = Select(driver.find_element_by_name("toMonth")) select.select_by_visible_text(to_month) select = Select(driver.find_element_by_name("toYear")) select.select_by_visible_text(to_year)
Problem 3 – JavaScript
Jedyne, co pozostało w formularzu, to „kliknięcie” przycisku Znajdź, aby rozpocząć wyszukiwanie. Było to trochę trudne, ponieważ przycisk Znajdź wydawał się być kontrolowany przez JavaScript i nie był zwykłym przyciskiem typu „Wyślij”. Sprawdzając go w narzędziach programistycznych, znalazłem obraz przycisku i mogłem uzyskać jego ścieżkę XPath, klikając prawym przyciskiem myszy.
Następnie uzbrojony w te informacje znalazłem element na stronie, po czym go kliknąłem.
# Find the 'Find' button, then click it driver.find_element_by_xpath("/html/body/table/tbody/tr[2]/td[1]/table[3]/tbody/tr[2]/td[2]/input").click()
I voila, formularz został przesłany i pojawiły się dane! Teraz mógłbym po prostu zeskrobać wszystkie dane na stronie wyników i zapisać je zgodnie z wymaganiami. Czy mógłbym?
Uzyskiwanie danych
Najpierw musiałem załatwić sprawę, w której przeszukanie nic nie znalazło. To było całkiem proste. Wyświetli komunikat w formularzu wyszukiwania bez opuszczania go, coś w rodzaju „Nie znaleziono rekordów”. Po prostu szukałem tego ciągu i zatrzymałem się, jeśli go znalazłem.
Ale jeśli pojawiły się wyniki, dane były prezentowane w div ze znakiem plus (+), aby otworzyć transakcję i pokazać wszystkie jej szczegóły. Otwarta transakcja zawierała znak minus (-), który po kliknięciu zamykał div. Kliknięcie znaku plus wywołałoby adres URL, aby otworzyć jego div i zamknąć każdy otwarty.
W związku z tym konieczne było znalezienie wszelkich znaków plus na stronie, zebranie adresu URL obok każdego z nich, a następnie przejście przez każdy z nich, aby uzyskać wszystkie dane dla każdej transakcji.
# Loop through transactions and count links = driver.find_elements_by_tag_name('a') link_urls = [link.get_attribute('href') for link in links] thisCount = 0 isFirst = 1 for url in link_urls: if (url.find("GetXas.do?processId") >= 0): # URL to link to transactions if isFirst == 1: # already expanded + isFirst = 0 else: driver.get(url) # collapsed +, so expand # Find closest element to URL element with correct class to get tran type tran_type=driver.find_element_by_xpath("//*[contains(@href,'/retail/transaction/results/GetXas.do?processId=-1')]/following::td[@class='txt_75b_lmnw_T1R10B1']").text # Get transaction status status = driver.find_element_by_class_name('txt_70b_lmnw_t1r10b1').text # Add to count if transaction found if (tran_type in ['Move In','Move Out','Switch']) and (status == "Complete"): thisCount += 1
W powyższym kodzie pola, które pobrałem, to typ transakcji i status, a następnie dodane do licznika, aby określić, ile transakcji pasuje do określonych reguł. Mogłem jednak pobrać inne pola w szczegółach transakcji, takie jak data i godzina, podtyp itp.
W przypadku tego projektu licznik został zwrócony z powrotem do aplikacji wywołującej. Jednak to i inne zeskrobane dane mogły być również przechowywane w płaskim pliku lub w bazie danych.
Dodatkowe możliwe przeszkody i rozwiązania
Podczas scrapingu nowoczesnych stron internetowych za pomocą własnej instancji przeglądarki może pojawić się wiele innych przeszkód, ale większość z nich można rozwiązać. Tu jest kilka:
Próbuję znaleźć coś, zanim się pojawi
Przeglądając siebie, jak często zauważasz, że czekasz na pojawienie się strony, czasami przez wiele sekund? Cóż, to samo może się zdarzyć podczas nawigacji programowej. Szukasz klasy lub innego elementu – a go tam nie ma!
Na szczęście Selenium ma możliwość czekania, aż zobaczy określony element i może przekroczyć limit czasu, jeśli element się nie pojawi, na przykład:
element = WebDriverWait(driver, 10). until(EC.presence_of_element_located((By.ID, "theFirstLabel")))
Jak przejść przez Captcha
Niektóre witryny wykorzystują Captcha lub podobne, aby zapobiegać niechcianym robotom (które mogą Cię uznać za). Może to utrudnić zdrapywanie wstęgi i spowolnić je.
W przypadku prostych podpowiedzi (takich jak „co to jest 2 + 3?”) można je ogólnie łatwo odczytać i rozgryźć. Jednak w przypadku bardziej zaawansowanych barier istnieją biblioteki, które mogą pomóc w próbie ich złamania. Niektóre przykłady to 2Captcha, Death by Captcha i Bypass Captcha.
Zmiany strukturalne strony internetowej
Strony internetowe mają się zmieniać – i często tak się dzieje. Dlatego pisząc skrypt do scrapingu, najlepiej o tym pamiętać. Zastanów się, jakich metod użyjesz do znalezienia danych, a których nie. Rozważ częściowe dopasowywanie technik zamiast próbować dopasowywać całą frazę. Na przykład strona internetowa może zmienić komunikat z „Nie znaleziono rekordów” na „Nie znaleziono rekordów” – ale jeśli twoje dopasowanie to „Brak rekordów”, powinno być w porządku. Zastanów się również, czy dopasować do XPATH, identyfikatora, nazwy, tekstu linku, nazwy tagu lub klasy lub selektora CSS – i która jest najmniejsza do zmiany.
Podsumowanie: Python i Selenium
To była krótka demonstracja, która pokazała, że prawie każdą witrynę można zeskrobać, bez względu na to, jakie technologie są używane i jakie są złożoności. Zasadniczo, jeśli możesz samodzielnie przeglądać witrynę, generalnie można ją zeskrobać.
Teraz, jako zastrzeżenie, nie oznacza to, że każda strona internetowa powinna zostać zeskrobana. Niektóre mają uzasadnione ograniczenia i było wiele spraw sądowych, w których orzekano o legalności skrobania niektórych witryn. Z drugiej strony, niektóre witryny są mile widziane i zachęcają do pobierania danych z ich witryny, aw niektórych przypadkach udostępniają interfejs API, aby ułatwić.
Tak czy inaczej, najlepiej sprawdzić warunki przed rozpoczęciem jakiegokolwiek projektu. Ale jeśli pójdziesz dalej, możesz mieć pewność, że wykonasz zadanie.
Zalecane zasoby dotyczące kompleksowego pobierania danych z sieci:
- Zaawansowane drapanie stron internetowych w Pythonie: najlepsze praktyki i obejścia
- Skalowalne skrobanie „zrób to sam”: jak budować i uruchamiać skrobaki na dużą skalę