Синтаксический анализ html из рендеринга JavaScript с помощью объекта Python

Я хотел бы извлечь рыночную информацию из следующего URL и всех последующих страниц:

https://uk.reuters.com/investing/markets/index/.FTSE?sortBy=&sortDir=&pn=1

Я успешно проанализировал нужные данные с первой страницы, используя некоторый код из следующего URL:

https://impythonist.wordpress.com/2015/01/06/ultimate-guide-for-scraping-javascript-rendered-web-pages

Я также был в состоянии разобрать URL для следующей страницы для подачи в цикл, чтобы получить данные со следующей страницы. Проблема в том, что он падает до загрузки следующей страницы по причине, которую я не до конца понимаю.

У меня есть предчувствие, что класс, который я позаимствовал у "импитониста", может быть причиной проблемы. Я не знаю достаточно объектно-ориентированного программирования, чтобы решить проблему. Вот мой код, большая часть которого заимствована из приведенного выше URL:

import sys  
from PyQt4.QtGui import *  
from PyQt4.QtCore import *  
from PyQt4.QtWebKit import *  
from lxml import html
import re
from bs4 import BeautifulSoup

class Render(QWebPage):  
  def __init__(self, url):  
    self.app = QApplication(sys.argv)  
    QWebPage.__init__(self)  
    self.loadFinished.connect(self._loadFinished)  
    self.mainFrame().load(QUrl(url))  
    self.app.exec_()  

  def _loadFinished(self, result):  
    self.frame = self.mainFrame()  
    self.app.quit()  



base_url='https://uk.reuters.com'
complete_next_page='https://uk.reuters.com/investing/markets/index/.FTSE?sortBy=&sortDir=&pn=1'

#LOOP TO RENDER PAGES AND GRAB DATA
while complete_next_page != '':
    print ('NEXT PAGE: ',complete_next_page, '\n')
    r = Render(complete_next_page)  # USE THE CLASS TO RENDER JAVASCRIPT FROM PAGE
    result = r.frame.toHtml()     # ERROR IS THROWN HERE ON 2nd PAGE

# PARSE THE HTML
soup = BeautifulSoup(result, 'lxml')
row_data=soup.find('div', attrs={'class':'column1 gridPanel grid8'})
print (len(row_data))

# PARSE ALL ROW DATA
stripe_rows=row_data.findAll('tr', attrs={'class':'stripe'})
non_stripe_rows=row_data.findAll('tr', attrs={'class':''})
print (len(stripe_rows))
print (len(non_stripe_rows))

# PARSE SPECIFIC ROW DATA FROM INDEX COMPONENTS
#non_stripe_rows: from 4 to 18 (inclusive) contain data
#stripe_rows: from 2 to 16 (inclusive) contain data
i=2
while i < len(stripe_rows):
    print('CURRENT LINE IS: ',str(i))
    print(stripe_rows[i])
    print('###############################################')
    print(non_stripe_rows[i+2])
    print('\n')
    i+=1

#GETS LINK TO NEXT PAGE
next_page=str(soup.find('div', attrs={'class':'pageNavigation'}).find('li', attrs={'class':'next'}).find('a')['href']) #GETS LINK TO NEXT PAGE WORKS
complete_next_page=base_url+next_page

Я аннотировал фрагменты кода, которые я написал и понял, но я действительно не знаю, что происходит в классе 'Render', достаточно, чтобы диагностировать ошибку? Разве это что-то еще?

Вот ошибка:

result = r.frame.toHtml()
AttributeError: 'Render' object has no attribute 'frame'

Мне не нужно хранить информацию в классе, как только я ее проанализирую, поэтому я подумал, что, возможно, ее можно будет как-то очистить или сбросить, а затем обновить, чтобы она содержала новую информацию URL-адреса со страницы 2:n, но я понятия не имею, как сделать это?

В качестве альтернативы, если кто-то знает другой способ получить эти конкретные данные с этой и следующих страниц, то это было бы одинаково полезно?

Спасибо заранее.

1 ответ

Решение

Как насчет использования селена и фантомов вместо PyQt.
Вы можете легко получить селен, выполнив "pip install selenium". Если вы используете Mac, вы можете получить phantomjs, выполнив "brew install phantomjs". Если ваш компьютер под управлением Windows, используйте choco вместо brew, или Ubuntu используйте apt-get.

from selenium import webdriver
from bs4 import BeautifulSoup

base_url = "https://uk.reuters.com"
first_page = "/business/markets/index/.FTSE?sortBy=&sortDir=&pn=1"

browser = webdriver.PhantomJS()

# PARSE THE HTML
browser.get(base_url + first_page)
soup = BeautifulSoup(browser.page_source, "lxml")
row_data = soup.find('div', attrs={'class':'column1 gridPanel grid8'})

# PARSE ALL ROW DATA
stripe_rows = row_data.findAll('tr', attrs={'class':'stripe'})
non_stripe_rows = row_data.findAll('tr', attrs={'class':''})
print(len(stripe_rows), len(non_stripe_rows))

# GO TO THE NEXT PAGE
next_button = soup.find("li", attrs={"class":"next"})
while next_button:
  next_page = next_button.find("a")["href"]
  browser.get(base_url + next_page)
  soup = BeautifulSoup(browser.page_source, "lxml")
  row_data = soup.find('div', attrs={'class':'column1 gridPanel grid8'})
  stripe_rows = row_data.findAll('tr', attrs={'class':'stripe'})
  non_stripe_rows = row_data.findAll('tr', attrs={'class':''})
  print(len(stripe_rows), len(non_stripe_rows))
  next_button = soup.find("li", attrs={"class":"next"})

# DONT FORGET THIS!!
browser.quit()

Я знаю, что приведенный выше код неэффективен (я чувствую, что он слишком медленный), но я думаю, что он принесет вам желаемые результаты. Кроме того, если веб-страница, которую вы хотите очистить, не использует Javascript, даже PhantomJS и селен не нужны. Вы можете использовать модуль запросов. Однако, так как я хотел показать вам контраст с PyQt, я использовал PhantomJS и Selenium в этом ответе.

Другие вопросы по тегам