Reguliere expressies gebruiken in adblock


firefox logo Als je op deze pagina bent gekomen is de kans vrij groot dat je Firefox gebruikt samen met adblock. Adblock is een erg handige extensie voor firefox om (nagenoeg) reclamevrij te kunnen surfen. Naast de gebruikelijke wildcards kent adblock ook een erg krachtig mechanisme om te filteren: reguliere expressies. Ik heb op deze pagina een poging gedaan een heldere uitleg te geven van hoe ze werken en hoe je ze kunt gebruiken. Hoewel je ongetwijfeld in het voordeel zult zijn als je zelf kunt programmeren is dit absoluut niet nodig om deze tutorial goed te kunnen begrijpen. Het is slechts dat er concepten in voorkomen waar je als programmeur misschien al mee te maken hebt gehad. Heb je adblock nog niet, maar lijkt reclamevrij surfen je wel erg interessant, dan moet je even bij mijn extensievertalingen kijken ;) .

Wat is dat nou, zo’n reguliere expressie?


Reguliere expressies, ook wel regexps of regular expressions genoemd, beschrijven talen of patronen. In de informatica worden ze gebruikt in parsers om te bepalen wat er nu precies staat in een stuk programmacode, XML, HTML enzovoorts. Nu klinkt het een stuk ingewikkelder dan dat het is, hetgene waarin we hier geinteresseerd zijn is dat we met een reguliere expressie een verzameling strings kunnen samenvatten met één patroonomschrijving. Eerst zullen we bekijken hoe je in een reguliere expressie een patroon omschrijft, daarna wordt uitgelegd hoe je bepaalde URLs simpel in één patroon kunt vatten om in zo min mogelijk expressies zoveel mogelijk ongewenste content te filteren.
Hier komt wat informaticajargon in voor. Met een karakter wordt een teken bedoeld. Dit kunnen letters zijn, cijfers, punten, spaties… Een string is een rij van karakters. Elk stuk tekst is te beschouwen als een string, dus ook URLs die we al dan niet willen filteren.

Adblock werkt intern met javascript, dus de expressies hebben ook deze zelfde syntax, welke op zijn beurt geleend is van de taal Perl. Helaas zijn de reguliere expressies van javascript iets minder krachtig dan die van Perl, waarbij het grootste gemis de afwezigheid van negaties van expressies is. Het is dus helaas op dit moment niet mogelijk om het filter voor bepaalde sites uit te schakelen. Gelukkig is het sinds kort mogelijk om een whitelist te definiëren, hoewel deze niet tot de syntax van javascript reguliere expressies behoren.

Klinkt ingewikkeld, hoe werken die expressies nu?


De syntax van reguliere expressies is vrij simpel, ze bestaan uit karakters en metakarakters. De gehele expressie staat op zijn beurt tussen slashes. De expressie wordt van links naar rechts afgewerkt. Als er karakters staan (zoals letters), wordt gekeken of op een punt deze letter in de te matchen string staat. Slaagt dit, dan wordt naar het volgende (meta)karakter in de expressie gekeken. Dit moet staan direct na het punt in de te matchen string waar je was gebleven na het vorige (meta)karakter in de expressie. Is dat niet zo, dan is er geen match. Je hebt een match als het lukt ergens in de te matchen string alle regels van de expressie van links naar rechts te doorlopen. Dit klinkt allemaal misschien zeer formeel, maar in wezen komt het neer op niet meer dan dit:
/click/ matcht: elke string waar click letterlijk in staat.

Er zullen vast mensen zijn die zich afvragen wat hier bijzonder aan is. Niet veel natuurlijk… maar er zijn nog veel meer mogelijkheden. Je kunt in reguliere expressies aangeven dat meerdere dingen mogelijk zijn, het ene, het andere, of beide. Als je wel eens geprogrammeerd hebt: inderdaad een or. Hier komen we een metakarakter tegen, in dit geval de operator |. Met deze operator kun je een keuze maken tussen wat aan weerszijden staat, uiteraard mogen ook beide opties voorkomen.
/banner|advert/ matcht: www.bannerswap.com, advertisement.falkag.net, www.banners.com/advertising

Da’s leuk, nu kun je al je losse adblockregels met |’s achterelkaar plakken in één regel. Maar da’s nog lang niet alles. Je hebt ook haakjes waarmee je een deel van de expressie kunt groeperen. Iedereen die wiskunde gehad heeft op school weet hoe haakjes zich in formules gedragen: ze gaan voor alles als je ze oplost. Dit geldt ook voor reguliere expressies: als je aankomt bij een gedeelte tussen haakjes is hetgene tussen haakjes hetgene dat in z’n geheel gematcht moet worden voordat je na het haakjespaar verder gaat. Zowel een los karakter als een expressie tussen haakjes kun je beschouwen als subexpressie. In de praktijk zul je haakjes vaak gebruiken in combinatie met de |.
/falkag(jpg|gif)/ matcht: alles waar falkagjpg of falkaggif in staat.

Dit begint al op een bruikbaar filter te lijken, maar het zou fijn zijn als we wat flexibeler om konden gaan met wat er tussen bepaalde subexpressies mag staan. De wildcards van de simpele adblockfilters zijn te implementeren met behulp van quantifiers en tekenklassen. Klinkt ingewikkeld… is het niet. Een tekenklasse representeert één karakter uit een bepaalde verzameling, een quantifier geeft aan hoevaak de subexpressie waar hij achter staat gematcht moet worden. We introduceren de metakarakters ., *, ? en +. De . staat voor één willekeurig karakter, de * achter een subexpressie wil zeggen dat deze nul of meer keren mag matchen, de ? dat de expressie waar hij achter staat nul of één keer mag matchen en de + voor minstens één match. Enkele voorbeelden zullen dingen verduidelijken.
/im.ges/ matcht met images, imoges, im1ges, maar niet met imges of imaaages.
/im.*ges/ matcht met images, imoges, im1ges, imges of imaaages.
/(yay)+/ matcht met yay, yayyay, yayyayyayyayyay, maar niet met een lege string.
/pic(server)?z0r/ matcht met picz0r en picserverz0r, maar niet met picserverserverz0r.

Geloof het of niet, zojuist zijn de belangrijkste gereedschappen beschreven voor het maken van geavanceerde filters. Er zijn echter nog wat mogelijkheden die je minder vaak gebruikt, maar wel handig zijn. Eén van deze dingen zijn zelfgemaakte quantifiers. Met behulp van van een accoladepaar achter een subexpressie heb je meer controle over het aantal keren voorkomen ervan.
{3}: match de voorgaande expressie drie keer.
{2, 5}: match de voorgaande expressie minimaal 2, maximaal vijf keer.
{5, }: match de voorgaande expressie minimaal vijf keer.

Ook soms nuttig zijn de verdere tekenklassen, waarvan we de . al gezien hebben. Waar je op moet letten is dat een tekenklasse in je expressie in beginsel één match betekent. In combinatie met quantifiers worden het krachtige instrumenten, voor ons nuttige voorgedefiniëerde zijn:
\w: grote/kleine letters, cijfers en de underscore.
\W: alles wat niet in \w zit.
\d: een cijfer.
\D: een niet-cijfer.

Het is ook mogelijk om zelf een verzameling aan te geven die als tekenklasse dient. Dit doe je door losse karakters of bereiken van karakters tussen blokhaken te zetten. Ook kun je met behulp van de operator ^ aangeven dat je alles behalve de verzameling wilt matchen. Voorbeelden:
[abcwe]: een van deze letters.
[a-f]: een kleine letter van a t/m f.
[J-L]: een hoofdletter van J t/m L.
[3-9]: een cijfer van 3 t/m 9.
[^a-j]: geen kleine letter van a t/m j.
[a-cD-F]: een kleine letter van a t/m c of een hoofdletter van D t/m F.
/promo[^z]/: uit m’n eigen filterlijst: op deze manier heb ik ervoor gezorgd dat promozilla.nl niet ten onrechte geblokkeerd wordt.

Let op: metakarakters mag je binnen de blokhaken niet escapen, de ], - en ^ wel. Van de laatste twee is het niet strikt nodig als je ze handig neerzet, maar het kan geen kwaad.

Wat verder nog speciale karakters zijn zijn de symbolen voor het begin en het einde van de regel, respectievelijk ^ en $. Door een ^ in je expressie te zetten geef je aan dat er niks voor dat punt mag staan, met een $ dat er niks na het punt mag staan. Wil je dus een filter maken met file-extensies kun je er uit veiligheid een $ achterzetten.
Het kan ook voorkomen dat je één van de genoemde metakarakters letterlijk wilt filteren. Dit doe je door er een \ voor te zetten: het escapekarakter. Daar URLs veel karakters bevatten die als metakarakter gezien kunnen worden zul je de \ regelmatig nodig hebben.
.: het metakarakter voor een willekeurig karakter.
\.: een letterlijke punt.
\/: een letterlijke slash.
\w: een woordkarakter.
\\w: letterlijk \w.
\\\w: een letterlijke \ gevolgd door een woordkarakter.
\\\\w: letterlijk \\w.

Daadwerkelijk filters maken


Dit is alle functionaliteit van reguliere expressies die we kunnen gebruiken voor het maken van goede filters. Ikzelf maak een onderscheid tussen twee ’soorten’:
  • Het filteren van woorden die je niet in de url wilt zien (ad, banner en dergelijke);
  • Het uitschakelen van advertentiebronnen voor een specifieke website.
Een derde optie, het blokkeren van hosts die altijd advertenties bieden, is te scharen onder woorden die je niet wilt zien.

Om te laten zien hoe je een filter van de eerste categorie kunt opbouwen geef ik als voorbeeld een vereenvoudiging van één van m’n eigen filters die allerlei iteraties van ad filtert. /ad/ voldoet niet omdat je dan ook sad, mouse_pad en dergelijke dingen filtert.
/(page|site|web)+ad(server|js|vertenties?|s|vert(ise(ment|ing)?)?s?)+/
In dit filter staat ad in het midden, links ervan moet een van de items page, site of web staan. Rechts van ad staat een lijst dingen die erop moeten volgen, probeer zelf na te gaan welke woorden dit filter allemaal mee matcht.

Als je bij een bepaalde website de advertentiebronnen wilt aanpakken zul je wat analyse moeten doen aan de opbouw van de site. We nemen als advertentievergeven voorbeeldsite http://www.cu2.nl en gaan daarin in de eerste plaats zoeken naar iframes, scripts en plaatjes. Kijk ook goed in welke subdirectories de normale plaatjes en de reclame staan, veel sites houden er aparte directories voor aan, iets waar je gebruik van kunt maken.
We komen bij een willekeurig bezoek aan de site de volgende reclame-elementen tegen die zij zelf hosten:
  • http://addict.servers.cu2.nl/js/1/addict.js
  • http://addict.servers.cu2.nl/ads/79.gif
  • http://images.cu2.nl/cu2.nl/js/ticker.js?1099685688437
Ook URLs met ‘feed’ staan soms in de lijst van elementen die je niet wilt. Een gegeneraliseerd filter voor de ongewenste content wordt dan:

/cu2.*(ticker|feeds?|addict)/

Custom filters voor sites zijn vooral de moeite waard als je die site vaak bezoekt. Wat echter vervelend is, is dat sommige sites alle plaatjes (advertenties en nuttige) in dezelfde directory hebben staan. Aangezien advertenties telkens wisselen is het ondoenlijk om deze elk apart in het filter op te nemen, de pagina-layout is een stuk minder veranderlijk en we zouden willen dat we wat uitzonderingen konden aangeven. Dat kan. Maar in zeer beperkte mate.
Een feature die adblock geërfd heeft van Perl is lookahead assertion. In adblock kunnen we, nadat het filter is doorlopen, erna nog uitzonderingen aangeven. Deze staan in een subexpressie tussen (?! ). Erna mogen geen filterregels meer staan.
/banner(?!.*mysite\.gif)/: werkt wel, als er ergens na banner nog mysite.gif opduikt is er geen match.
/banner.*(?!mysite\.gif)/: werkt niet, de .* matcht namelijk met alles, en na de .* is de string op en kan de uitzondering niet gematcht worden.
/(?!mysite\.com).*banner/: werkt niet. Deze techniek is dus helaas niet te gebruiken om filters uit te schakelen voor websites die je wilt steunen door hun advertenties te accepteren…

Ookal is dit mechanisme in vergelijking met de mogelijkheden in Perl zeer beperkt, het is afdoende om een goed filter te maken voor de zelf gehoste reclame van startkabel.nl. Een zeer nuttige methode om na te gaan wat gewenste URLs zijn en wat niet is het naar de site surfen en op het adblock-knopje in Firefox klikken. Als je uit de lijst een URL selecteert gaat het object namelijk knipperen, wat het eenvoudig maakt na te gaan welke URL bij welk plaatje hoort en dus ook om na te gaan wat de URLs zijn van de plaatjes die je wilt behouden.
We zien:
  • Alle plaatjes staan in /images/ en sommige URLs van plaatjes die we willen behouden bevatten een dubbele slash.
  • Plaatjes die bij de site horen en niet samen te vatten zijn:
    • boven.gif
    • logo.gif
    • w.gif
    • startpaginabar.gif
    • De flash-klok
  • De plaatjes in de header hebben twee variaties: -a en -o.
  • De plaatjes in de footer kunnen aan of uit zijn, er staan één of twee streepjes in hun URLs.
En ziehier het uiteindelijke aan de hand van deze observaties gemaakte filter (één regel, maar zo past ie in het window):

/startkabel.*\/images\/(?!(\/)?(boven\.gif|logo\.gif|w\.gif|klok.*\.swf|
.*footer-{1,2}(uit|aan)|startpaginabar\.gif|(home|paginas|forums|ezine)-[ao]\.gif))/

Dingen wél doorlaten: de whitelist


Sinds kort heeft adblock de mogelijkheid om een whitelist aan te leggen. Deze werken heel simpel: zet @@ voor de expressie en alle items die eraan voldoen zullen altijd getoond worden. Omdat deze whitelist nog steeds op het niveau van de URL van de objecten werkt zul je hiervan ook uit moeten zoeken hoe ze in elkaar zitten; websites opgeven waarop helemaal geen filtering plaats moet vinden is niet mogelijk in de filters. Nieuwere versies van Adblock Plus hebben echter een aparte lijst van sites waarop geen filtering toegepast wordt. En een lijst van sites waarvan de advertenties wel geladen maar niet getoond worden. Aan alles is gedacht ;) .

Samenvatting van de speciale karakters


Op verzoek hier nog een overzicht van alle speciale karakters om alles achteraf nog eens rustig te kunnen overkijken :) .
/Een reguliere expressie staat tussen twee slashes, één aan het begin, één aan het eind.
|Deze operator geeft een keuze: net als de logische or. Links en rechts van de | moet matchen, beide mag ook, maar is niet verplicht. a|b: een a of een b.
()Haakjes zet je om een subexpressie heen als je deze wilt afscheiden van de rest, bijvoorbeeld om op het geheel een quantifier gebruiken of in combinatie met | om aan te geven wat aan weerszijden bij de | hoort en wat niet. (aap|noot), (huh)*.
.De tekenklasse . is synoniem voor een willekeurig karakter, behalve een regeleinde.
*Een quantifier: nul of meer voorkomens van de subexpressie die ervoor staat. a* matcht met a, aaaaaaaaa of niks.
+Minimaal één voorkomen van de subexpressie die ervoor staat.
?Exact nul of één voorkomens van de subexpressie die ervoor staat.
{}Tussen accolades kun je zelf een quantifier maken door er getallen tussen te zetten, zoals {4}, {2,5} of {4,}.
[]Tussen blokhaken kun je een eigen tekenklasse maken.
^Binnen blokhaken ontkent de ^ een tekenklasse. Daarbuiten staat dit karakter voor het begin van de invoerstring. /^b/ zegt dat de string met een b moet beginnen.
$Dit karakter staat voor het einde van de invoerstring. /b$/ zegt dat de string met een b moet eindigen.
-Binnen blokhaken geef je met de - een bereik aan, zoals [j-l].
\Met de backslash escape je een metakarakter zodat dit als een letterlijk karakter wordt opgevat. De backslash zelf is ook een metakarakter.
(?! )Lookahead assertion. Zelden bruikbaar, maar soms nuttig. Aan het eind van je reguliere expressie kun je hierin uitzonderingen opgeven, maar alleen aan het eind.
@@Zet @@ voor een reguliere expressie om deze als een whitelistitem aan te merken.

Samenvatting gebruikte begrippen


Expressie: Een uitdrukking, bij reguliere expressies beschrijven deze uitdrukkingen patronen. Elke subexpressie is zelf ook een expressie. Tekenklassen, karakters en stukken tussen haakjes zijn subexpressies en op elke subexpressie kun je een quantifier gebruiken en elke subexpressie kan als operand dienen.
Karakter: Een teken. Letters, cijfers, accenttekens, spaties, interpunctietekens zijn allemaal karakters. Regeleinden en sommige speciale commando’s ook, maar die laten we buiten beschouwing.
Match: Een stuk invoer voldoet aan het patroon in de reguliere expressie.
Metakarakter: metakarakters hebben binnen een reguliere expressie een speciale betekenis, tenzij je er de \ voorzet om ze te escapen.
Operator: De quantifiers en de |. De operanden (waarop een operator werkt) van een operator zijn expressies.
Patroon: Reguliere expressies zijn formele omschrijvingen van patronen. Patronen zouden in natuurlijke taal iets luiden als “ad met eventueel een s erachter, daarna maakt het niet uit totdat je falkag of akamai tegenkomt en het moet eindigen op gif of jpeg”. Als er echter iets is wat computers niet kunnen is het interpreteren van natuurlijke taal, daarom zit je deze tutorial ook te lezen :P .
Quantifier: Geeft aan hoevaak een subexpressie voorkomt.
String: Een rij karakters. URLs die we willen bekijken zijn allemaal string. De lege string bevat geen karakters. Een karakter is een string met één element, twee strings achterelkaar zijn ook een string, elk deel van een string is een substring en zelf ook een string.
Tekenklasse: Een verzameling tekens, met elk van de tekens in die verzameling kan gematcht worden.

Tot slot…


Ik hoop dat dit een zinnige tutorial was. Als je foutjes hebt gevonden of aanvullingen/opmerkingen hebt hoor ik ze graag! Als je inspiratie nodig hebt (of lui bent :P ) kun je mijn filters eens bekijken en in adblock importeren. Er staan expressies in voor bepaalde woorden, bepaalde hosts en specifieke filters voor bepaalde websites en reclame van gratis hosts. Ik moet erbij zeggen dat ik niet perfect ben en sommige filters misschien handiger kunnen. /affiliates?/ heeft een overbodige s? bijvoorbeeld, ga zelf maar na waarom ;) .

Get Thunderbird!Get Firefox!