[Python爬虫]利用Selenium等待Ajax加载及模拟自动翻页,爬取东方财富网公司公告

1.背景

首先,打开东方财富网公司公告页面(“http://data.eastmoney.com/notices/”)。


单击右键,选择检查“长江电力”处的源代码,如图:


点击右键,查看源代码,查找“长江电力”,并没有在html代码里面找到“长江电力”,而只是在js代码找到。所以,可以判断,该网页采用了Ajax技术,用js动态去加载新的数据。所以,问题一:解决获得通过Ajax动态加载的数据。

然后,点击下一页,网页的url没有变化,其只是用js去加载了新的数据,然后动态改变了Table里面的数据。所以,第二个问题是:模拟翻页,也就是模拟点击网页中的“下一页”按钮。


2.解决方法

1)通过Ajax动态加载的数据

现在的网页越来越多采用了Ajax技术,这样程序便不能确定何时某个元素完全加载出来。这会让元素定位困难而且会提高产生ElementNotVisibleException的概率。

所以,Selenium提供了两种等待方式,一种是隐式等待,一种是显式等待。

隐式等待是等待特定的时间,显示等待是指定某一条件直到这个条件成立时继续执行。在这里,采用显示等待的方式,等待Table中的数据动态加载。

显示等待指定某个条件,然后设置最长等待时间。如果在这个时间还没有找到元素,那么便会抛出异常。

def singlePageCraw(self, driver):
        try:
            element = WebDriverWait(driver, 10).until(
                EC.presence_of_element_located((By.ID, "dt_1"))
            )
            tr_options = element.find_elements_by_tag_name("tr")
            df = pd.DataFrame(columns = ['title', 'content'] )
            list = ['id','name','title','detail','type','time']
            for tr_option in tr_options:
                td_options = tr_option.find_elements_by_tag_name("td")
                res_data = []
                stockid = ''
                filename = ''
                count = 0
                for td_option in td_options:
                    if '更多公告 股吧 研报' in td_option.text:
                        continue
                    df.loc[count] = [list[count], str(td_option.text).strip()]
                    count = count + 1
                    if str.isdigit(str(td_option.text).strip()):
                        stockid = td_option.text
                    if ':' in td_option.text:
                        detail_url = td_option.find_element_by_tag_name("a").get_attribute('href')
                        html_cont = self.downloader.download(detail_url)
                        detail_content = self.parser.parse(detail_url, html_cont)
                        df.loc[count] = [list[count], str(detail_content).strip()]
                        filename = stockid + '_' + detail_url.split(',')[0].split('/')[-1]
                        count = count + 1
                print filename
                df.to_csv("D:/"+ filename +'.csv', index=False)
                
        except Exception,e:
            print str(e)

其中,

element = WebDriverWait(driver, 10).until(
                EC.presence_of_element_located((By.ID, "dt_1"))
            )

便是显示等待。程序默认会每500ms调用一次来查看元素是否已经生成,如果本来元素就是存在的,那么会立即返回。

分析页面结构可知,id为"dt_1"的table元素加载后,数据就会全部加载完毕了。接下来便是根据自己的需要,选择元素,获得元素的属性或者值。

关于元素的选取,有如下的API:

1)单个元素选取

find_element_by_id
find_element_by_name
find_element_by_xpath
find_element_by_link_text
find_element_by_partial_link_text
find_element_by_tag_name
find_element_by_class_name
find_element_by_css_selector

2)多个元素选取

find_elements_by_name
find_elements_by_xpath
find_elements_by_link_text
find_elements_by_partial_link_text
find_elements_by_tag_name
find_elements_by_class_name
find_elements_by_css_selector

关于元素中值的获取:

1)获取元素的值

option.text

2)获取元素的属性值

option.get_attribute('href')

在这里,获取到了动态加载的元素之后,通过选择元素的API,获取元素的值或者属性值。

具体情况具体分析:


可以看到,在table中,每一行中,一共有六列。过滤掉第三列“更多公告 股吧 研报”,对于第四列,不仅要保存标题,还需要保存点击链接后网页中的内容。

在这里,有一个选择,一种方法是同样采用Selenium,打开网页,选择其中的元素来获取内容;另一种方法是使用urllib2下载网页,再用html.parser解析网页提取内容。考虑到打开网页需要的时间较长,而且通过分析,具体公告的内容是直接加载的,所以,采用第二种方法。

下载网页:

# coding:utf8

import urllib2
import socket
import sys

reload(sys)
sys.setdefaultencoding('utf-8')

class HtmlDownloader(object):
      
    def download(self,url):
        socket.setdefaulttimeout(200)
        if url is None:
            return None
        
        response = urllib2.urlopen(url)
        
        if response.getcode() != 200:
            return None
        return unicode(response.read(), 'GB18030', 'ignore').encode('UTF-8')
        #return response.read()


解析网页:

# coding:utf8

from baike_spider import html_downloader
from bs4 import BeautifulSoup
import sys
 
reload(sys)
sys.setdefaultencoding('utf-8')

class HtmlParser(object):
    
    def __init__(self):
        self.downloader = html_downloader.HtmlDownloader()
                   
    def _get_new_data(self, page_url, soup):
        try:   
            #<div class="detail-body" style="padding: 20px 50px;">
            return soup.find('div', class_ = 'detail-body').find('div').get_text()
        except Exception,e:
            print e
    
    def parse(self, page_url, html_cont):
        if page_url is None or html_cont is None:
            return
        
        soup = BeautifulSoup(html_cont, 'html.parser', from_encoding='utf-8')
        return self._get_new_data(page_url, soup)


2)模拟自动翻页

可以看到,点击下一页,网页的url没有发生变化。使用Selenium,模拟点击“下一页”,再使用和前面一样的方法,选择元素,就可以了。

WebDriverWait(driver, 10).until(
                EC.presence_of_element_located((By.XPATH, "//a[contains(text(),'下一页')]"))
            ).click()
            time.sleep(5)


注意:这里可能会出现一个错误:

"org.openqa.selenium.WebDriverException: Element is not clickable at point (411, 675). Other element would receive the click: ..."

具体的原因和解决方法这里有详细的解释:

http://stackoverflow.com/questions/11908249/debugging-element-is-not-clickable-at-point-error

大致分为以下几种:

1.The element is not visible to click.(网页元素不可见)

Use Actions or JavascriptExecutor for making it to click.

By Actions:

WebElement element = driver.findElement(By("element_path"));

Actions actions = new Actions(driver);

actions.moveToElement(element).click().perform();

By JavascriptExecutor:

JavascriptExecutor jse = (JavascriptExecutor)driver;

jse.executeScript("scroll(250, 0)"); // if the element is on top.

jse.executeScript("scroll(0, 250)"); // if the element is on bottom.

or

JavascriptExecutor jse = (JavascriptExecutor)driver;

jse.executeScript("arguments[0].scrollIntoView()", Webelement); 

Then click on the element.

2.The page is getting refreshed before it is clicking the element.(点击元素的时候网页正在刷新)

For this, make the page to wait for few seconds.

3. The element is clickable but there is a spinner/overlay on top of it(网页上有其他元素遮挡了原本要点击的元素)

The below code will wait until the overlay disppears

By loadingImage = By.id("loading image ID");

WebDriverWait wait = new WebDriverWait(driver, timeOutInSeconds);

wait.until(ExpectedConditions.invisibilityOfElementLocated(loadingImage));
结合情况1和情况2,我对上述代码进行了如上处理。


3.小结

这就是爬取东方财富网的全部要点了。这也是我学会的第二项爬虫技能。

在这里,要感谢:

Python爬虫利器五之Selenium的用法

python利用beautifulsoup+selenium自动翻页抓取网页内容

这两篇博文的作者。

完整代码课参考:

Python爬取东方财富网公司公告



阅读更多
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u013250416/article/details/61425207
个人分类: python
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭