Capítulo 3

Introdução

Nos capítulos anteriores, o Web Scraping foi mostrado de forma mais "artificial", em sites mais simples e compostos por páginas únicas. Neste capítulo, veremos scrapers sendo utilizados em problemas do mundo real.

Percorrendo um único domínio

Tendo como exemplo o Six Degrees of Wikipedia - jogo que consiste na associação de artigos da Wikipedia ligados entre si -, o livro propõe um simples script Python que obtém uma lista de links presentes em uma página da Wikipedia.

from urrlib.request import urlopen
from bs4 import BeautifulSoup

html = urlopen('http://en.wikipedia.org/wiki/Kevin_Bacon')
bs = BeautifulSoup(html, 'html.parser')
for link in bs.find_all('a'):
  if 'href' in link.attrs:
    print(link.attrs['href'])

Analisando a lista gerada, vemos que alguns itens indesejados apareceram, como links para caixas de texto e até para rodapés:

//wikimediafoundation.org/wiki/Privacy_policy
//en.wikipedia.org/wiki/Wikipedia:Contact_us

Os links que estávamos procurando - os que apontam para outras páginas de artigo - têm as seguintes características em comum:

  • Estão na div com id bodyContent

  • Os URLs não contém dois-pontos

  • Os URLs começam com /wiki/

Com esse padrão em mente, podemos revisar o código usando a expressão regular ^(/wiki/)((?!:).)*$"):

from urllib.request import urlopen
from bs4 import BeautifulSoup
import re

html = urlopen('http://en.wikipedia.org/wiki/Kevin_Bacon')
bs = BeautifulSoup(html, 'html.parser')
for link in bs.find('div', {'id':'bodyContent'}).find_all(
  'a', href=re.compile('^(/wiki/)((?!:).)*$')):
  if 'href' in link.attrs:
    print(link.attrs['href'])

Executando esse código, obtemos uma lista de todos os URLs de artigo para os quais o artigo inicial da Wikipedia aponta. Embora interessante, essa ideia pode ser inútil na prática. Portanto, podemos fazer uma atualização:

  • Função getLinks que recebe um URL de um artigo no formato /wiki/ e devolve uma lista com os URLS de outros artigos associados

  • Função principal que chame getLinks, escolha um link aleatório na página e chame getLinks novamente, até que o programa seja interrompido ou nada seja encontrado

from urllib.request import urlopen
from bs4 import BeautifulSoup
import datetime
import random
import re
  
random.seed(datetime.datetime.now())
def getLinks(articleUrl):
  pythtml = urlopen('http://en.wikipedia.org{}'.format(articleUrl))
  bs = BeautifulSoup(html, 'html.parser')
  return bs.find('div', {'id':'bodyContent'}).find_all('a',
    href=re.compile('^(/wiki/)((?!:).)*$'))

links = getLinks('/wiki/Kevin_Bacon')
while len(links) > 0:
  newArticle = links[random.randint(0, len(links)-1)].attrs['href']
  print(newArticle)
  links = getLinks(newArticle)

O corpo principal do programa define uma lista de tags de links. Depois, o laço encontra uma tag de link aleatória para outro artigo na página, extraindo o href dela, exibindo a página e obtendo uma nova lista de links.

Rastreando um site completo

Rastrear um site completo pode ser conveniente para várias tarefas, como para gerar um mapa do site em questão ou para coleta de dados. A abordagem inicial consiste em procurar uma página inicial e ir "descendo" para os links mais internos. Porém, se cada página tiver 10 links internos, por exemplo, e o site tiver uma profundidade de 5 páginas, o número total de páginas que deverão ser rastreadas é de 100.000. Isso deve-se ao fato de que muitos links internos são duplicados, ou seja, precisamos evitar que uma mesma página seja rastreada mais de uma vez. Usaremos conjuntos.

from urllib.request import urlopen
from bs4 import BeautifulSoup
import re
  
pages = set()
def getLinks(pageUrl):
  global pages
  html = urlopen('http://en.wikipedia.org{}'.format(pageUrl))
  bs = BeautifulSoup(html, 'html.parser')
  for link in bs.find_all('a', href=re.compile('^(/wiki/)')):
    if 'href' in link.attrs:
      if link.attrs['href'] not in pages:
        #Encontramos uma página nova
        newPage = link.attrs['href']
        print(newPage)
        pages.add(newPage)
        getLinks(newPage)
getLinks('')

Aqui, o scraper procura todos os links que comecem com /wiki/, independente de qualquer outro fator.

Coletando dados de um site completo

Se quiséssemos construir um scraper que colete título, primeiro parágrafo do conteúdo e um link para editar a página (caso exista), precisaríamos observar algumas páginas para estabelecer um padrão:

  • Todas os títulos estão em tags h1 -> span

  • Todo o texto do corpo encontra-se na div#bodyContent

  • Links para edição só estão presentes em páginas de artigo, sob a tag li#ca-edit, em li#ca-edit -> span -> a

Modificando o código anterior:

from urllib.request import urlopen
from bs4 import BeautifulSoup
import re
  
pages = set()
def getLinks(pageUrl):
  global pages
  html = urlopen('http://en.wikipedia.org{}'.format(pageUrl))
  bs = BeautifulSoup(html, 'html.parser')
  try:
    print(bs.h1.get_text())
    print(bs.find(id ='mw-content-text').find_all('p')[0])
    print(bs.find(id='ca-edit').find('span')
      .find('a').attrs['href'])
  except AttributeError:
    print('This page is missing something! Continuing.')
  for link in bs.find_all('a', href=re.compile('^(/wiki/)')):
    if 'href' in link.attrs:
      if link.attrs['href'] not in pages:
      #Encontramos uma página nova
      newPage = link.attrs['href']
      print('-'*20)
      print(newPage)
      pages.add(newPage)
      getLinks(newPage)
getLinks('')

Rastreando pela internet

Antes de escrever um crawler, é importante definir

  • Quais dados você quer obter

  • Quando o crawler alcançar um site em particular, ele segue para o próximo link ou permanece no mesmo site para explorar outros níveis?

  • Há alguma condição para não coletar um site em particular?

  • E a parte legal/judicial da coisa? (MUITO IMPORTANTE!)

Também é importante definir funções Python que, quando combinadas, podem executar vários tipos de web scraping:

from urllib.request import urlopen
from urllib.parse import urlparse
from bs4 import BeautifulSoup
import re
import datetime
import random
  
pages = set()
random.seed(datetime.datetime.now())
  
#Obtém uma lista de todos os links internos encontrados em uma página
def getInternalLinks(bs, includeUrl):
  includeUrl = '{}://{}'.format(urlparse(includeUrl).scheme,
    urlparse(includeUrl).netloc)
internalLinks = []
#Encontra todos os links que começam com "/"
for link in bs.find_all('a',
    href=re.compile('^(/|.*'+includeUrl+')')):
    if link.attrs['href'] is not None:
       if link.attrs['href'] not in internalLinks:
          if(link.attrs['href'].startswith('/')):
             internalLinks.append(
                includeUrl+link.attrs['href'])
          else:
            internalLinks.append(link.attrs['href'])
return internalLinks 
  
#Obtém uma lista de todos os links externos encontrados em uma página
def getExternalLinks(bs, excludeUrl):
  externalLinks = []
  #Encontra todos os links que começam com "http" e que
  #não contenham o URL atual
  for link in bs.find_all('a',
    href=re.compile('^(http|www)((?!'+excludeUrl+').)*$')):
    if link.attrs['href'] is not None:
      if link.attrs['href'] not in externalLinks:
         externalLinks.append(link.attrs['href'])
return externalLinks
  
def getRandomExternalLink(startingPage):
  html = urlopen(startingPage)
  bs = BeautifulSoup(html, 'html.parser')
  externalLinks = getExternalLinks(bs,
  urlparse(startingPage).netloc)
if len(externalLinks) == 0:
  print('No external links, looking around the site for one')
  domain = '{}://{}'.format(urlparse(startingPage).scheme,
    urlparse(startingPage).netloc)
  internalLinks = getInternalLinks(bs, domain)
  return getRandomExternalLink(internalLinks[random.randint(0,
                        len(internalLinks)-1)])
else:
  return externalLinks[random.randint(0, len(externalLinks)-1)]
def followExternalOnly(startingSite):
  externalLink = getRandomExternalLink(startingSite)
  print('Random external link is: {}'.format(externalLink))
  followExternalOnly(externalLink)

followExternalOnly('http://oreilly.com')

Esse programa começa em http://oreilly.com e pula aleatoriamente de um link externo para outro, tendo como saída, por exemplo: http://igniteshow.com/ http://feeds.feedburner.com/oreilly/news http://hire.jobvite.com/CompanyJobs/Careers.aspx?c=q319 http://makerfaire.com/

Nem sempre podemos garantir que links externos serão encontrados na primeira página de um site. Nesse caso, um método utilizado é o de explorar recursivamente os níveis de um site até encontrar um link externo. A vantagem de separar tarefas em funções simples é que o código pode ser facilmente atualizado no futuro se quisermos executar uma tarefa de rastreamento diferente da original.

Last updated