Follow Us

Scraping do Instagram com Python

Scraping do Instagram com Python

Já tentou extrair informações de algum site mas na hora de olhar o código fonte se deparou com algo parecido com isso?

Código fonte de um perfil do instagram

Isso acontece porque muitos sites hoje em dia, como o Instagram, utilizam frameworks em que o conteúdo não vem na requisição da página html, e sim por requisições futuras em javascript. Dessa maneira as páginas ficam mais “leves” e são carregadas mais rapidamente.

Então como obter essas informações? 🤔

Uma biblioteca como a requests nos dá apenas o código fonte da página, antes da execução do javascript. Precisamos de algo que renderize a página como um navegador faz. Uma das opções mais utilizadas é a biblioteca Selenium!

Atenção: Este artigo é de caráter educacional e não deve ser utilizado para obter conteúdos protegidos por direitos autorais.

Instagram

Perfil oficial do Instagram

Vamos ver então como obter as seguintes informações a partir de um nome de usuário utilizando Python, a biblioteca Selenium e o driver do Chrome:

  • Números – publicações, seguidores e seguindo
  • Informações dos posts – foto, localização e descrição

Nesse projeto, faremos algumas considerações:

  • Os perfis acessados devem ser públicos assim não precisamos nos autenticar
  • Usaremos a abordagem do melhor esforço (best-effort) para obter as informações porque alguns posts não possuem localização ou comentários
  • Caso as informações não existam ou ocorra algum erro os atributos terão valores padrão

Instalação

Esta instalação foi testada e homologada no seguinte ambiente:

Para instruções detalhadas da instalação você pode dar uma olhada no README.md deste projeto no github.

Código

Antes de mais nada, vamos usar o ArgumentParser para que possamos adicionar argumentos ao nosso script main.py.

from argparse import ArgumentParser

parser = ArgumentParser()
parser.add_argument('-u', '--username', dest='username', help='Profile username')
parser.add_argument('-d', '--debug', dest='debug', default=False, required=False, help='If True it shows debug output')
args = parser.parse_args()

Criando modelos

Vamos criar duas classes para armazenar as informações de um perfil e de um post em um arquivo models.py.

Essas classes não são essenciais e você poderia guardar as informações em listas ou dicionários, mas a orientação a objetos sempre nos ajuda a manter um código mais limpo e organizado.

Criamos também um método save(), em cada classe, para salvar os dados em arquivos de texto.

class Profile:
    
    def __init__(self, username):
        self.username = username
        
        self.name = ''
        self.num_posts = 0
        self.num_followers = 0
        self.num_following = 0
        
        self.directory = 'data/' + username
        
        if not os.path.exists(self.directory):
            os.makedirs(self.directory)
            
        open('{}/posts.csv'.format(self.directory), 'w+')
        
    def save(self):
        
        with open('{}/data.csv'.format(self.directory), 'w+') as f:
            
            f.write('"{}"; "{}"\n'.format('name', self.name))
            f.write('"{}"; "{}"\n'.format('num_posts', self.num_posts))
            f.write('"{}"; "{}"\n'.format('num_followers', self.num_followers))
            f.write('"{}"; "{}"\n'.format('num_following', self.num_following))
class Post:
    
    def __init__(self, profile):
        self.profile = profile
        self.id_ = 0
        self.url = ''
        
        self.lat = 0
        self.lng = 0
        self.desc = ''
        
    def save(self):
        
        with open('{}/posts.csv'.format(self.profile.directory), 'a') as f:          
            f.write('"{}"; "{}"; "{}"; "{}"\n'.format(
                self.id_, self.lat, self.lng, self.desc))

Instanciando o Chrome

Agora podemos inicializar uma instância do Chrome para obter as informações e acessar o perfil do usuário.

options = Options()
options.add_argument('--headless')
options.add_argument('--window-size=1920x1080')
driver = '/usr/bin/chromedriver'

chrome = Chrome(
    chrome_options=options, 
    executable_path=driver
)

chrome.get('https://www.instagram.com/' + args.username)
if args.debug: print('Chrome running at ', chrome.current_url)

Você vai reparar que temos alguns argumentos a serem passados. São eles:

  • headless: para que o navegador inicialize sem uma interface, possibilitando rodar em um Linux Server;
  • window-size: para que o site seja renderizado em modo desktop e todos os elementos que querermos estejam visíveis na página; e
  • driver: o caminho para o chrome driver baixado anteriormente.

Bom, agora que já temos uma instância do chrome aberta no perfil do Instagram, vamos começar a selecionar os elementos HTML de onde queremos extrair informações.

O Selenium nos proporciona diversas maneiras de obter dados dos elementos (id, classe, nome, seletor CSS, XPath, …).

Vamos utilizar os seletores CSS mas poderíamos escolher qualquer outro em que fosse possível localizar os elementos.

Função de copiar seletores do Google Chrome

Uma dica muito boa para ter uma ideia do seletor CSS é abrir o Google Chrome, na aba de inspecionar o código, clicar como botão direito e ir no menu Copy.

Uma nota sobre os seletores CSS: evite utilizar nomes de classes como g47SY ou -vDIg em seus seletores pois provavelmente elas são geradas automaticamente por algum framework e podem mudar a qualquer momento, quebrando o seu código. Procure utilizar seletores formados pela estrutura hierárquica do site, pois ela tem menos chances de mudar ao longo do tempo.

Vamos ver os seletores dos objetos que queremos:

selectors = {
    'name': 'header h1',
    'num_posts': 'header ul li:nth-child(1) span',
    'num_followers': 'header ul li:nth-child(2) span',
    'num_following': 'header ul li:nth-child(3) span',
    
    'posts': 'main article a',
    'desc': 'article ul li[role=menuitem] div span:not([role=link])',
    'img': 'main article > div img',
    'local': 'header a[href*=locations]',
    'lat': 'meta[property*=latitude]',
    'lng': 'meta[property*=longitude]',
}

Obtendo dados do perfil

Para obter os dados do perfil precisamos apenas dos seletores que já descobrimos para usar o método chrome.find_element_by_css_selector() e remover as virgulas, as strings “mil” e “milhão” para limpar os números.

profile = Profile(args.username)

name_el = chrome.find_element_by_css_selector(selectors['name'])
profile.name = name_el.text

num_posts_el = chrome.find_element_by_css_selector(selectors['num_posts'])
profile.num_posts = int(num_posts_el.text.replace(',', ''))

num_followers_el = chrome.find_element_by_css_selector(selectors['num_followers'])
profile.num_followers = int(num_followers_el.text.replace(',', '').replace('mil', '').replace('milhões', ''))

num_following_el = chrome.find_element_by_css_selector(selectors['num_following'])
profile.num_following = int(num_following_el.text.replace(',', ''))

if args.debug: print('Saved', profile)
profile.save()

Obtendo os posts

Aqui temos um problema, o Instagram só carrega os primeiros posts por padrão e para ver os próximos o usuário precisa rolar (scroll) a página para baixo. Então vamos precisar simular essa ação usando javascript.

Precisamos rolar a página até que o número de posts que aparecem seja igual ao número total que já obtemos.

urls = []
while len(urls) < profile.num_posts:

    if args.debug: print('Scroll down... ', end='')
    chrome.execute_script('window.scrollTo(0, document.body.scrollHeight)')
    sleep(1)

    for a in chrome.find_elements_by_css_selector( selectors['posts'] ):
        href = a.get_attribute('href')
        if href not in urls:
            urls.append(href)

    if args.debug: print('found', len(urls), 'of', profile.num_posts)

Note que ao mesmo tempo que novos posts aparecem conforme rolamos a página, os antigos vão sendo removidos. O Instagram provavelmente faz isso para deixar a página mais leve.

Agora que temos as url dos posts podemos acessar cada página e obter suas informações:

for i, url in enumerate(urls):

    post = Post(profile)
    post.id_ = i
    post.url = url

    chrome.get(url)
    if args.debug: chrome.get_screenshot_as_file('data/{}/post{}.png'.format(profile.username, i))

    # pega a descrição do post que é o primeiro comentário
    desc_el = chrome.find_elements_by_css_selector(selectors['desc'])
    if len(desc_el) > 0:
        post.desc = desc_el[0].text.replace('\n', ' ')

    # pega a imagem
    img_el = chrome.find_element_by_css_selector(selectors['img'])
    post.download_img(img_el.get_attribute('src'))

    # pega o link do local, e depois a latitude e longitude
    local_el = chrome.find_elements_by_css_selector(selectors['local'])
    if len(local_el) > 0:
        href = local_el[0].get_attribute('href')

        if 'locations' in href:
            chrome.get(href)
            if args.debug: chrome.get_screenshot_as_file('data/{}/loc{}.png'.format(profile.username, i))

            lat_metas = chrome.find_elements_by_css_selector(selectors['lat'])
            if len(lat_metas) > 0:
                post.lat = lat_metas[0].get_attribute('content')

            lng_metas = chrome.find_elements_by_css_selector(selectors['lng'])
            if len(lng_metas) > 0:
                post.lng = lng_metas[0].get_attribute('content')

    if args.debug: print('Saved', post) 
    post.save()

Falta apenas implementarmos o método download_img() da classe Post que, como o nome já diz, faz o download da imagem.

class Post:

    def download_img(self, src):
        filename = '{}/{}.png'.format(self.profile.directory, self.id_)
        urllib.request.urlretrieve(src, filename)

Por fim, não podemos esquecer de finalizar a execução do nosso driver do chrome:

chrome.quit()

Pronto! Agora já podemos testar nosso código!

$ python main.py --username=usuario --debug=True

Conclusão

A biblioteca Selenium facilita, e muito, o trabalho de scraping em sites muito dinâmicos como  o Instagram.

Tenha sempre em mente que quando estamos lidando com scraping de páginas da internet somos reféns de qualquer mudança ou atualização que possa ocorrer.

Por isso é importante sempre manter seu código atualizado e realizar testes.

Código

O código completo deste artigo está disponível no github

Referências

  1. Documentação oficial do Selenium.
  2. Download do Chrome Driver.

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *