Verkeersbesluiten scrapen

28 november 2020

Voor een artikel was ik op zoek naar verkeersbesluiten waarmee de maximumsnelheid wordt aangepast. Het downloaden van de resultaten van een zoekopdracht is niet zo’n probleem (maar pas op bij het gebruik van filters bij zoekopdrachten: je loopt het risico om relevante besluiten te missen). Om vervolgens de relevante gegevens uit de html te halen is wat lastiger.

Besluiten hebben een min of meer standaard opbouw. Eerst komen meestal de overwegingen, dan een kopje zoals BESLUIT of Besluiten, en dan het eigenlijke besluit. De onderstaande functie checkt of een regel het begin van het eigenlijke besluit aankondigt.

def decision_starts(line):
    """Check if line announces start of decision"""
    if line.startswith('B E S L U I'):
        return True
    line = ''.join([c for c in line if c.isalpha()]).lower()
    for string in ['besluittot', 'besluitentot', 'besluitenwij']:
        if line.startswith(string):
            return True
    return line in ['besluit', 'besluiten', 'hetbesluit']

De functie houdt rekening met verschillende manieren waarop het besluit aangekondigd kan worden. Merk op dat er ook rekening wordt gehouden met een vrij rare variant, namelijk dat een regel begint met de tekens B E S L U I (inclusief spaties tussen de letters). Dit is vanwege pagina’s waar je dit soort code tegenkomt:

<span class="vet _p_nadrukvet">B E S L U I T E</span>
<span class="vet _p_nadrukvet"><!---->
<span class="vet _p_nadrukvet">N :</span>

Ik vermoed dat iemand hier de tekst uit een pdf heeft gekopieerd en die online heeft gezet. Als je de pagina met je browser bekijkt ziet het er redelijk normaal uit, maar achter de schermen is de html-code een rommeltje. Bovendien komen er verschillende varianten voor; ik ben zelfs een variant tegengekomen waarin de eerste regel de letter B bevat en de regel erna de rest van het woord. Daar valt nauwelijks nog tegenop te coderen.

Ik denk dat m’n functie in de meeste gevallen op een correcte manier een knip maakt tussen de overwegingen en het daadwerkelijke besluit. In de gevallen waar dat eventueel fout is gegaan, bestaat er een risico dat regels uit de overwegingen ten onrechte geïnterpreteerd worden als besluit. Verderop in de code probeer ik dat enigszins te ondervangen door regels die met dat beginnen uit te sluiten: overwegingen bestaan immers vaak uit een opsomming van regels die met dat beginnen.

Als je eenmaal de tekst van het besluit hebt, is het zaak om daaruit te destilleren wat er is besloten. In besluiten over maximumsnelheid staat meestal dat er een bepaald type bord is verplaatst of verwijderd. Als je een code zoals A01-30 of A1 (zone 30) tegenkomt, weet je eigenlijk al genoeg.

Maar je kan ook zinnen tegenkomen zoals Door het plaatsen van verkeersborden conform model A1 en A2 van Bijlage I van het RVV 1990, aan te wijzen een maximum snelheid van 30 km/u op een deel van de Ceramiquelaan en de Bercylaan. Als in de regel een snelheidsaanduiding (bijvoorbeeld 30km) voorkomt plus ofwel een relevante bordcode (bijvoorbeeld A1), ofwel een term als maximumsnelheid, dan reken ik deze ook mee.

Hier enkele reguliere expressies om verschillende varianten op te pikken:

PATTERN_KM_SIGN = r'A\s?0?1[\-]?([0-9]{2})'
PATTERN_KM_ZONE = r'A\s?0?1\s?\(zone ([0-9]{2})\)'
PATTERN_KM = r'[^0-9]([0-9]{2,3})\s?k[mi]'
PATTERN_SIGN = r'[Aa]\s?\.?0?1'

Wat je dan wel nog wil weten, is of het bord is geplaatst of juist verwijderd. Daarvoor heb ik simpelweg gecheckt of termen zoals plaatsing, instellen of intrekken in de tekst voorkomen. Dat kan wel eens foutgaan; zo bevat dit besluit de term verwijderen, maar het betreft een besluit waarin een eerder besluit ongedaan wordt gemaakt.

Een verkeersbesluit kan meerdere maatregelen omvatten. Zo wordt in dit Amsterdamse verkeersbesluit in één keer op een stuk of dertig plaatsen 30 km ingevoerd. Er zijn ook verkeersbesluiten die verschillende soorten maatregelen bevatten. Om te voorkomen dat zaken door elkaar gaan lopen, knip ik de besluiten eerst in regels op, om daarna regel voor regel na te gaan wat voor maatregel er wordt genomen.

De complete code voor de scraper vind je hier.

28 november 2020 | Categoriën: howto, python