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 urlopenfrom bs4 import BeautifulSouphtml =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:
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 urlopenfrom bs4 import BeautifulSoupimport rehtml =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
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 urlopenfrom bs4 import BeautifulSoupimport repages =set()defgetLinks(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']notin 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 urlopenfrom bs4 import BeautifulSoupimport repages =set()defgetLinks(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'])exceptAttributeError: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']notin 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 urlopenfrom urllib.parse import urlparsefrom bs4 import BeautifulSoupimport reimport datetimeimport randompages =set()random.seed(datetime.datetime.now())#Obtém uma lista de todos os links internos encontrados em uma páginadefgetInternalLinks(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']isnotNone:if link.attrs['href']notin 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áginadefgetExternalLinks(bs,excludeUrl): externalLinks = []#Encontra todos os links que começam com "http" e que#não contenham o URL atualfor link in bs.find_all('a', href=re.compile('^(http|www)((?!'+excludeUrl+').)*$')):if link.attrs['href']isnotNone:if link.attrs['href']notin externalLinks: externalLinks.append(link.attrs['href'])return externalLinksdefgetRandomExternalLink(startingPage): html =urlopen(startingPage) bs =BeautifulSoup(html, 'html.parser') externalLinks =getExternalLinks(bs,urlparse(startingPage).netloc)iflen(externalLinks)==0:print('No external links, looking around the site for one') domain ='{}://{}'.format(urlparse(startingPage).scheme,urlparse(startingPage).netloc) internalLinks =getInternalLinks(bs, domain)returngetRandomExternalLink(internalLinks[random.randint(0,len(internalLinks)-1)])else:return externalLinks[random.randint(0, len(externalLinks)-1)]deffollowExternalOnly(startingSite): externalLink =getRandomExternalLink(startingSite)print('Random external link is: {}'.format(externalLink))followExternalOnly(externalLink)followExternalOnly('http://oreilly.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.