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

来源:互联网 发布:最好的二手交易软件 编辑:程序博客网 时间:2024/06/10 18:40

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_idfind_element_by_namefind_element_by_xpathfind_element_by_link_textfind_element_by_partial_link_textfind_element_by_tag_namefind_element_by_class_namefind_element_by_css_selector

2)多个元素选取

find_elements_by_namefind_elements_by_xpathfind_elements_by_link_textfind_elements_by_partial_link_textfind_elements_by_tag_namefind_elements_by_class_namefind_elements_by_css_selector

关于元素中值的获取:

1)获取元素的值

option.text

2)获取元素的属性值

option.get_attribute('href')

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

具体情况具体分析:


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

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

下载网页:

# coding:utf8import urllib2import socketimport sysreload(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:utf8from baike_spider import html_downloaderfrom bs4 import BeautifulSoupimport 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爬取东方财富网公司公告



0 0