Ga naar hoofdinhoud

Webcrawlers maken met Scrapy voor Python

Ontwikkel webcrawlers met Scrapy, een krachtig framework voor het extraheren, verwerken en opslaan van webdata.
· 14 min lezen

Verken met AI

Wil je een overzicht van webscraping in Python, volg dan DataCamp's cursus Web Scraping with Python.

In deze tutorial leer je hoe je Scrapy gebruikt, een Python-framework waarmee je grote hoeveelheden data kunt verwerken! Je leert Scrapy door een webscraper te bouwen voor AliExpress.com, een e-commercewebsite. Aan de slag met scrappen!

Basiskennis van HTML en CSS helpt om deze tutorial makkelijker en sneller te begrijpen. Lees dit artikel als opfrisser over HTML en CSS.

Scrapy-overzicht

scrapy overview
Bron

Webscraping is uitgegroeid tot een effectieve manier om informatie van het web te halen voor besluitvorming en analyse. Het is een essentieel onderdeel van de toolkit van elke data scientist. Data scientists moeten weten hoe ze data van webpagina's verzamelen en die data in verschillende formaten opslaan voor verdere analyse.

Elke webpagina die je op internet ziet, kan worden gecrawld voor informatie en alles wat zichtbaar is op een webpagina kan worden geëxtraheerd [2]. Elke webpagina heeft zijn eigen structuur en webelementen, waardoor je je webcrawlers/spiders moet schrijven afgestemd op de pagina die je extraheert.

Scrapy biedt een krachtig framework om data te extraheren, te verwerken en vervolgens op te slaan.

Scrapy gebruikt spiders, zelfstandige crawlers die een set instructies krijgen [1]. In Scrapy is het makkelijker om grote crawlprojecten te bouwen en op te schalen doordat ontwikkelaars hun code kunnen hergebruiken.

Scrapy vs. BeautifulSoup

In deze sectie krijg je een overzicht van een van de meest gebruikte webscrapingtools, BeautifulSoup, en een vergelijking met Scrapy.

Scrapy is een Python-framework voor webscraping dat een compleet pakket biedt voor ontwikkelaars, zonder zorgen over het onderhoud van code.

Beautiful Soup wordt ook veel gebruikt voor webscraping. Het is een Python-pakket om HTML- en XML-documenten te parsen en daar data uit te halen. Het is beschikbaar voor Python 2.6+ en Python 3.

Hier zijn in een notendop enkele verschillen:

ScrapyBeautifulSoup
Functionaliteit---
Scrapy is het complete pakket om webpagina's te downloaden, te verwerken en op te slaan in bestanden en databasesBeautifulSoup is in essentie een HTML- en XML-parser en vereist extra libraries zoals requests, urlib2 om URL's te openen en het resultaat op te slaan [6]
Leercurve---
Scrapy is een powerhouse voor webscraping en biedt veel manieren om een webpagina te scrapen. Het kost meer tijd om te leren en te begrijpen hoe Scrapy werkt, maar eenmaal geleerd, vereenvoudigt het het maken van webcrawlers en het draaien ervan met slechts één opdrachtregel. Expert worden in Scrapy vergt wat oefening en tijd om alle functionaliteiten te leren.BeautifulSoup is relatief makkelijk te begrijpen voor beginners in programmeren en kan kleinere taken razendsnel uitvoeren
Snelheid en belasting---
Scrapy kan grote klussen heel eenvoudig aan. Het kan een groep URL's in niet meer dan een minuut crawlen, afhankelijk van de grootte van de groep, en doet dit zeer soepel omdat het Twister gebruikt, dat asynchroon (niet-blokkerend) werkt voor gelijktijdigheid.BeautifulSoup wordt efficiënt gebruikt voor eenvoudige scrapingklussen. Het is trager dan Scrapy als je geen multiprocessing gebruikt.
Functionaliteit uitbreiden---
Scrapy biedt Item pipelines die je toestaan functies in je spider te schrijven om je data te verwerken, zoals valideren, verwijderen en opslaan in een database. Het biedt spider Contracts om je spiders te testen en stelt je in staat generieke en diepe crawlers te maken. Je kunt veel variabelen beheren, zoals retries, redirects, enzovoort.Als het project niet veel logica vereist, is BeautifulSoup geschikt, maar als je veel maatwerk nodig hebt zoals proxies, cookies beheren en datapijplijnen, is Scrapy de beste optie.

Informatie: Synchroon betekent dat je moet wachten tot een taak klaar is voordat je een nieuwe start, terwijl asynchroon betekent dat je naar een andere taak kunt gaan voordat de vorige is voltooid

Hier is een interessante DataCamp BeautifulSoup-tutorial om te leren.

Scrapy-installatie

scrapy

Met Python 3.0 (en hoger) geïnstalleerd, kun je als je anaconda gebruikt, conda gebruiken om scrapy te installeren. Typ de volgende opdracht in de anaconda-prompt:

conda install -c conda-forge scrapy

Om anaconda te installeren, bekijk deze DataCamp-tutorials voor Mac en Windows.

Als alternatief kun je de Python Package Installer pip gebruiken. Dit werkt voor Linux, Mac en Windows:

pip install scrapy

Scrapy Shell

Scrapy biedt ook een webcrawling-shell, de Scrapy Shell, die ontwikkelaars kunnen gebruiken om hun aannames over het gedrag van een site te testen. Laten we een webpagina voor tablets op de AliExpress-website nemen. Je kunt de Scrapy-shell gebruiken om te zien welke componenten de webpagina teruggeeft en hoe je die kunt gebruiken voor jouw doelen.

Open je command line en typ de volgende opdracht:

scrapy shell

Als je anaconda gebruikt, kun je bovenstaande opdracht ook in de anaconda-prompt typen. Je output op de command line of anaconda-prompt ziet er ongeveer zo uit:

run crawler

Je moet een crawler op de webpagina draaien met het fetch-commando in de Scrapy-shell. Een crawler of spider loopt een webpagina door en downloadt de tekst en metadata.

fetch(https://pt.aliexpress.com/category/201005406/special-store.html)

Let op: Zet een URL altijd tussen aanhalingstekens, zowel enkele als dubbele aanhalingstekens werken

De output ziet er als volgt uit:

output

De crawler geeft een response terug die je kunt bekijken met het commando view(response) in de shell:

view(response)

En de webpagina wordt geopend in de standaardbrowser.

web page

Je kunt het ruwe HTML-script bekijken met de volgende opdracht in Scrapy-shell:

print(response.text)

scrapy shell

Je ziet het script dat de webpagina genereert. Het is dezelfde inhoud die je ziet als je met de rechtermuisknop ergens op een lege plek op een webpagina klikt en view source of view page source kiest. Omdat je alleen relevante informatie uit het hele script nodig hebt, inspecteer je met de ontwikkelaarstools van de browser het vereiste element. Laten we de volgende elementen nemen:

  • Tabletnaam
  • Tabletprijs
  • Aantal bestellingen
  • Naam van de winkel

Klik met de rechtermuisknop op het element dat je wilt en klik op inspect zoals hieronder:

inspect

De ontwikkelaarstools van de browser helpen je enorm bij webscraping. Je ziet dat het een <a>-tag is met een class product en dat de tekst de productnaam bevat:

inspect

CSS-selectors gebruiken voor extractie

Je kunt dit extraheren via de elementattributen of de css-selector zoals classes. Schrijf het volgende in de Scrapy-shell om de productnaam te extraheren:

response.css(".product::text").extract_first()

De output is:

output

extract_first() haalt het eerste element op dat aan de css-selector voldoet. Wil je alle productnamen extraheren, gebruik dan extract():

response.css(".product::text").extract()

extract product names

De volgende code extraheert de prijsrange van de producten:

response.css(".value::text").extract()

extract product names

Op dezelfde manier kun je het aantal bestellingen en de naam van de winkel proberen.

XPath gebruiken voor extractie

XPath is een querytaal voor het selecteren van nodes in een XML-document [7]. Je kunt door een XML-document navigeren met XPath. Achter de schermen gebruikt Scrapy XPath om naar items in het HTML-document te navigeren. De CSS-selectors die je hierboven gebruikte, worden ook omgezet naar XPath, maar in veel gevallen is CSS heel eenvoudig te gebruiken. Toch is het goed om te weten hoe XPath in Scrapy werkt.

Ga naar je Scrapy Shell en schrijf fetch(https://pt.aliexpress.com/category/201005406/special-store.html/) zoals eerder. Probeer de volgende codefragmenten [3]:

response.xpath('/html').extract()

Dit laat je alle code zien onder de <html>-tag. / betekent direct kind van de node. Als je de <div>-tags onder de html-tag wilt ophalen, schrijf je [3]:

response.xpath('/html//div').extract()

Voor XPath moet je het gebruik van / en // begrijpen om te weten hoe je door child- en descendant-nodes navigeert. Hier is een handige tutorial over XPath-nodes en enkele voorbeelden om te proberen.

Als je alle <div>-tags wilt krijgen, kan dat door naar beneden te boren zonder /html te gebruiken [3]:

response.xpath("//div").extract()

Je kunt je beginnodes verder filteren en je gewenste nodes bereiken met behulp van attributen en hun waarden. Hieronder staat de syntaxis om classes en hun waarden te gebruiken.

response.xpath("//div[@class='quote']/span[@class='text']").extract()

response.xpath("//div[@class='quote']/span[@class='text']/text()").extract()

Gebruik text() om alle tekst binnen nodes te extraheren

Bekijk de volgende HTML-code:

Je wilt de tekst binnen de <a>-tag ophalen, die een child-node is van <div> met de classes site-notice-container container; dat kan als volgt:

response.xpath('//div[@class="site-notice-container container"]/a[@class="notice-close"]/text()').extract()

text inside tag

Een Scrapy-project en custom spider maken

Webscraping kan worden gebruikt om een aggregator te maken waarmee je data kunt vergelijken. Bijvoorbeeld: je wilt een tablet kopen en je wilt producten en prijzen naast elkaar vergelijken; dan kun je je gewenste pagina's crawlen en opslaan in een Excel-bestand. Hier ga je aliexpress.com scrapen voor informatie over tablets.

Nu maak je een custom spider voor dezelfde pagina. Eerst moet je een Scrapy-project aanmaken waarin je code en resultaten worden opgeslagen. Typ de volgende opdracht in de command line of anaconda-prompt.

scrapy startproject aliexpress

scrapy project

Dit maakt een verborgen map aan in je standaard Python- of anaconda-installatie. aliexpress wordt de naam van de map. Je kunt elke naam geven. Je kunt de mapinhoud direct via de verkenner bekijken. Hieronder staat de structuur van de map:

folder names
bestand/mapDoel
scrapy.cfgdeploy-configuratiebestand
aliexpress/Python-module van het project, van hieruit importeer je je code
__init.py__Initialisatiebestand
items.pyprojectitems-bestand
pipelines.pyprojectpipelines-bestand
settings.pyprojectinstellingenbestand
spiders/een map waar je later je spiders in plaatst
__init.py__Initialisatiebestand

Nadat je het project hebt aangemaakt, ga je naar de nieuw aangemaakte map en typ je de volgende opdracht:

[scrapy genspider aliexpress_tablets](https://pt.aliexpress.com/category/201005406/special-store.html)

command

Dit maakt een sjabloonbestand met de naam aliexpress_tablets.py in de map spiders, zoals hierboven besproken. De code in dat bestand is als volgt:

import scrapy

class AliexpressTabletsSpider(scrapy.Spider):
    name = 'aliexpress_tablets'
    allowed_domains = ['aliexpress.com']
    start_urls = ['https://www.aliexpress.com/category/200216607/tablets.html']


    def parse(self, response):
         pass

In de bovenstaande code zie je name, allowed_domains, sstart_urls en een parse-functie.

  • name: Name is de naam van de spider. Goede namen helpen je al je spiders bij te houden. Namen moeten uniek zijn, omdat ze worden gebruikt om de spider te draaien met scrapy crawl name_of_spider.
  • allowed_domains (optioneel): Een optionele Python-lijst met domeinen die gecrawld mogen worden. Verzoeken voor URL's die niet in deze lijst staan, worden niet gecrawld. Dit moet alleen het domein van de website bevatten (voorbeeld: aliexpress.com) en niet de volledige URL uit start_urls, anders krijg je waarschuwingen.
  • start_urls: Dit vraagt de opgegeven URL's op. Een lijst met URL's waar de spider begint te crawlen als er geen specifieke URL's zijn opgegeven [4]. De eerste pagina's die worden gedownload, zijn dus die welke hier vermeld staan. De daaropvolgende Requests worden achtereenvolgens gegenereerd uit data die in de start-URL's zit [4].
  • parse(self, response): Deze functie wordt aangeroepen telkens wanneer een URL succesvol is gecrawld. Hij wordt ook wel de callback-functie genoemd. De response (gebruikt in Scrapy-shell) die het resultaat is van het crawlen, wordt aan deze functie doorgegeven, en je schrijft de extractielogica erin!

Informatie: Je kunt BeautifulSoup gebruiken binnen de parse()-functie van de Scrapy-spider om het HTML-document te parsen.

Let op: Je kunt data extraheren via css-selectors met response.css() zoals besproken in de Scrapy Shell-sectie, maar ook met XPath (XML) waarmee je toegang hebt tot childelementen. Je ziet het voorbeeld van response.xpath() in de code die in de functie pass() is aangepast.

Je gaat wijzigingen aanbrengen in het bestand aliexpress_tablet.py. Ik heb nog een URL toegevoegd in start_urls. Je kunt de extractielogica toevoegen aan de pass()-functie zoals hieronder:

# -*- coding: utf-8 -*-
import scrapy


class AliexpressTabletsSpider(scrapy.Spider):
    name = 'aliexpress_tablets'
    allowed_domains = ['aliexpress.com']
    start_urls = ['https://www.aliexpress.com/category/200216607/tablets.html',
                 'https://www.aliexpress.com/category/200216607/tablets/2.html?site=glo&g=y&tag=']


    def parse(self, response):

        print("procesing:"+response.url)
        #Extract data using css selectors
        product_name=response.css('.product::text').extract()
        price_range=response.css('.value::text').extract()
        #Extract data using xpath
        orders=response.xpath("//em[@title='Total Orders']/text()").extract()
        company_name=response.xpath("//a[@class='store $p4pLog']/text()").extract()

        row_data=zip(product_name,price_range,orders,company_name)

        #Making extracted data row wise
        for item in row_data:
            #create a dictionary to store the scraped info
            scraped_info = {
                #key:value
                'page':response.url,
                'product_name' : item[0], #item[0] means product in the list and so on, index tells what value to assign
                'price_range' : item[1],
                'orders' : item[2],
                'company_name' : item[3],
            }

            #yield or give the scraped info to scrapy
            yield scraped_info

Informatie: zip() neemt n iterables en geeft een lijst met tuples terug. Het i-de element van de tuple wordt gemaakt met het i-de element uit elk van de iterables. [8]

Het sleutelwoord yield wordt gebruikt wanneer je een generatorfunctie definieert. Een generatorfunctie lijkt op een normale functie, behalve dat hij het sleutelwoord yield gebruikt in plaats van return. Het sleutelwoord yield wordt gebruikt wanneer de aanroepende functie een waarde nodig heeft, en de functie met yield behoudt zijn lokale staat en gaat verder waar hij was gebleven nadat hij een waarde had teruggegeven aan de aanroepende functie. Hier geeft yield de gegenereerde dictionary aan Scrapy, die het zal verwerken en opslaan!

Nu kun je de spider draaien:

scrapy crawl aliexpress_tablets

Je ziet een lange output op de command line zoals hieronder:

command
command

Data exporteren

Je wilt data vaak presenteren als een CSV of JSON zodat je die verder kunt gebruiken voor analyse. Deze sectie van de tutorial laat zien hoe je een CSV- en JSON-bestand voor deze data opslaat.

Om een CSV-bestand op te slaan, open je settings.py in de projectmap en voeg je de volgende regels toe:

FEED_FORMAT="csv"
FEED_URI="aliexpress.csv"

Nadat je settings.py hebt opgeslagen, draai je opnieuw scrapy crawl aliexpress_tablets in je projectmap.

project directory

Het CSV-bestand ziet er zo uit:

csv file

Let op: Elke keer dat je de spider draait, wordt het bestand aangevuld.

  • FEED_FORMAT [5]: Dit stelt het formaat in waarin je de data wilt opslaan. Ondersteunde formaten zijn:
         + JSON
         + CSV
         + JSON Lines
         + XML
    
  • FEED_URI [5]: Dit geeft de locatie van het bestand. Je kunt een bestand opslaan op je lokale opslag of ook op een FTP.

Scrapy's Feed Export kan ook een timestamp en de naam van de spider aan je bestandsnaam toevoegen, of je kunt deze gebruiken om een map te identificeren waarin je wilt opslaan.

  • %(time)s: wordt vervangen door een timestamp wanneer de feed wordt aangemaakt [5]
  • %(name)s: wordt vervangen door de naam van de spider [5]

Bijvoorbeeld:

  • Opslaan op FTP met één map per spider [5]:

ftp://user:[email protected]/scraping/feeds/%(name)s/%(time)s.json

De Feed-wijzigingen die je in settings.py aanbrengt, gelden voor alle spiders in het project. Je kunt ook aangepaste instellingen instellen voor een specifieke spider, die de instellingen in het bestand settings.py overschrijven.

# -*- coding: utf-8 -*-
import scrapy


class AliexpressTabletsSpider(scrapy.Spider):
    name = 'aliexpress_tablets'
    allowed_domains = ['aliexpress.com']
    start_urls = ['https://www.aliexpress.com/category/200216607/tablets.html',
                 'https://www.aliexpress.com/category/200216607/tablets/2.html?site=glo&g=y&tag=']

    custom_settings={ 'FEED_URI': "aliexpress_%(time)s.json",
                       'FEED_FORMAT': 'json'}

    def parse(self, response):

        print("procesing:"+response.url)
        #Extract data using css selectors
        product_name=response.css('.product::text').extract()
        price_range=response.css('.value::text').extract()
        #Extract data using xpath
        orders=response.xpath("//em[@title='Total Orders']/text()").extract()
        company_name=response.xpath("//a[@class='store $p4pLog']/text()").extract()

        row_data=zip(product_name,price_range,orders,company_name)

        #Making extracted data row wise
        for item in row_data:
            #create a dictionary to store the scraped info
            scraped_info = {
                #key:value
                'page':response.url,
                'product_name' : item[0], #item[0] means product in the list and so on, index tells what value to assign
                'price_range' : item[1],
                'orders' : item[2],
                'company_name' : item[3],
            }

            #yield or give the scraped info to Scrapy
            yield scraped_info

response.url geeft de URL terug van de pagina waarvan de response is gegenereerd. Na het draaien van de crawler met scrapy crawl aliexpress_tablets kun je het json-bestand bekijken:

json file

Je hebt vast gemerkt dat er twee links in de start_urls staan. De tweede link is pagina 2 van dezelfde tablet-zoekresultaten. Het is onpraktisch om alle links toe te voegen. Een crawler moet zelf door alle pagina's kunnen crawlen, en alleen het startpunt zou in de start_urls moeten staan.

Als een pagina vervolgpagina's heeft, zie je onderaan de pagina een navigator om vooruit en achteruit door de pagina's te gaan. In het geval dat je in deze tutorial gebruikt, zie je het er zo uitzien:

inspect

Hier is de code die je ziet:

code

Zoals je ziet staat er een <span>-tag met class .ui-pagination-active die de huidige pagina aangeeft, en daaronder staan alle <a>-tags met links naar de volgende pagina. Elke keer moet je de <a>-tags pakken die na deze <span>-tag komen. Hier komt een beetje CSS bij kijken! In dit geval moet je de sibling-node pakken en niet een child-node, dus je moet een css-selector maken die de crawler vertelt om <a>-tags te zoeken die na de <span>-tag met class .ui-pagination-active komen.

Onthoud: elke webpagina heeft zijn eigen structuur. Je moet de structuur een beetje bestuderen om te bepalen hoe je het gewenste element kunt ophalen. Probeer altijd eerst response.css(SELECTOR) in Scrapy Shell voordat je ze in code schrijft.

Pas je aliexpress_tablets.py aan zoals hieronder:


import scrapy


class AliexpressTabletsSpider(scrapy.Spider):
    name = 'aliexpress_tablets'
    allowed_domains = ['aliexpress.com']
    start_urls = ['https://www.aliexpress.com/category/200216607/tablets.html']


    custom_settings={ 'FEED_URI': "aliexpress_%(time)s.csv",
                       'FEED_FORMAT': 'csv'}

    def parse(self, response):

        print("procesing:"+response.url)
        #Extract data using css selectors
        product_name=response.css('.product::text').extract()
        price_range=response.css('.value::text').extract()
        #Extract data using xpath
        orders=response.xpath("//em[@title='Total Orders']/text()").extract()
        company_name=response.xpath("//a[@class='store $p4pLog']/text()").extract()

        row_data=zip(product_name,price_range,orders,company_name)

        #Making extracted data row wise
        for item in row_data:
            #create a dictionary to store the scraped info
            scraped_info = {
                #key:value
                'page':response.url,
                'product_name' : item[0], #item[0] means product in the list and so on, index tells what value to assign
                'price_range' : item[1],
                'orders' : item[2],
                'company_name' : item[3],
            }

            #yield or give the scraped info to scrapy
            yield scraped_info


            NEXT_PAGE_SELECTOR = '.ui-pagination-active + a::attr(href)'
            next_page = response.css(NEXT_PAGE_SELECTOR).extract_first()
            if next_page:
                yield scrapy.Request(
                response.urljoin(next_page),
                callback=self.parse)

In de bovenstaande code:

  • haal je eerst de link van de volgende pagina op met next_page = response.css(NEXT_PAGE_SELECTOR).extract_first() en als de variabele next_page een link krijgt en niet leeg is, gaat hij het if-blok in.

  • response.urljoin(next_page): De parse()-methode gebruikt deze methode om een nieuwe URL op te bouwen en een nieuw request te leveren, dat later naar de callback wordt gestuurd. [9]

  • Na het ontvangen van de nieuwe URL scrapt hij die link door de for-body uit te voeren en zoekt vervolgens opnieuw naar de volgende pagina. Dit gaat door totdat er geen volgende-pagina-link meer is.

Nu kun je achteroverleunen en je spider alle pagina's laten scrapen. De bovenstaande spider extraheert van alle vervolgpagina's. Dat wordt een hoop scraping! Maar je spider doet het voor je! Hieronder zie je dat de bestandsgrootte 1,1 MB heeft bereikt.

jupyter file

Scrapy doet het voor je!

In deze tutorial heb je geleerd over Scrapy, hoe het zich verhoudt tot BeautifulSoup, Scrapy Shell en hoe je je eigen spiders in Scrapy schrijft. Scrapy neemt je veel codewerk uit handen: van het aanmaken van projectbestanden en mappen tot het afhandelen van dubbele URL's. Het helpt je om krachtige webscraping in minuten op te zetten en biedt ondersteuning voor alle gangbare dataformaten die je verder in andere programma's kunt gebruiken. Deze tutorial helpt je zeker om Scrapy en het framework te begrijpen en wat je ermee kunt doen. Om een meester in Scrapy te worden, moet je alle fantastische functionaliteiten doornemen, maar deze tutorial heeft je in staat gesteld om groepen webpagina's efficiënt te scrapen.

Voor verdere verdieping kun je de Officiële Scrapy-documentatie raadplegen.

Vergeet ook niet DataCamp's cursus Web Scraping with Python te bekijken.

Referenties

Onderwerpen

Gerelateerd