最近一直在琢磨爬虫,从最早的BeautifulSoup爬取游民福利图,后来要爬取的动态网页多了,就逐渐过渡到了selenium+chromedriver/phantomJs的爬虫组合。偶然间听基友说有个msdn.itellyou.cn里收集了各种微软程序的ed2k安装包,因此便开始了对它的爬取之旅。

这个网站的页面结构还是挺复杂的。通过观察,发现软件的名称和地址都在右侧内容项中的label>checkbox中,名称为label的值,而地址为checkbox的data-url属性。且右侧的具体内容是通过div的动态加载来实现的,只有在点击了左侧的具体目录项才会出现;对于多语言的软件,每个子标签中的软件的xpath中的id又是不同的,因此需要动态获得每个子标签的id。最后,该网站会时不时地弹出呼吁捐赠的对话框,也会对爬虫造成影响。

由于采用selenium+chromedriver,且网站的各个资源项的id也摸不到规律,只能用最笨的方法——模拟点击法来获取所有软件的地址,即将目录项一个一个点开后再依次点击右侧的语言栏(若有的话);同时,在点击时还要处理随时可能弹出的捐款对话框。此外,有很多目录项里头其实没有数据,但也要花费一定时间点击。总的来说,这个爬虫效率是比较低的,若大家有更高效率的方法,欢迎提出。


源代码如下:

# -*- coding=utf-8 -*-
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
import time
from selenium.common.exceptions import NoSuchElementException,ElementNotVisibleException,WebDriverException,TimeoutException
import xlwt
#左侧目录的xpath
catalogue_list = ['//*[@id="accordion"]/div[1]/div[1]/h4/a',
                  '//*[@id="accordion"]/div[2]/div[1]/h4/a',
                  '//*[@id="accordion"]/div[3]/div[1]/h4/a',
                  '//*[@id="accordion"]/div[4]/div[1]/h4/a',
                  '//*[@id="accordion"]/div[5]/div[1]/h4/a',
                  '//*[@id="accordion"]/div[6]/div[1]/h4/a',
                  '//*[@id="accordion"]/div[7]/div[1]/h4/a',
                  '//*[@id="accordion"]/div[8]/div[1]/h4/a'
                  ]
catalogue_name = [u'企业解决方案',
                  u'MSDN技术资源库',
                  u'工具和资源',
                  u'应用程序',
                  u'开发人员工具',
                  u'操作系统',
                  u'服务器',
                  u'设计人员工具'
                  ]

count = 0
software_name_list = []
software_url_list = []


def builddriver(kind):
    chromedriver_path = 'D:\\WebDrivers\\chromedriver_win32\\chromedriver.exe'
    phantomjs_path = 'D:\\WebDrivers\\phantomjs\\bin\\phantomjs.exe'
    if kind == 'Chrome':
        driver = webdriver.Chrome(executable_path=chromedriver_path)
    elif kind == 'Phantomjs':
        driver = webdriver.PhantomJS(executable_path=phantomjs_path)
    return driver

#关闭弹出的捐款对话框
def clickdialog():
    global driver
    try:
        dialog_close_button = driver.find_element_by_xpath('/html/body/div[2]/div/div/div[1]/button')
        if dialog_close_button:
            dialog_close_button.click()
            time.sleep(3)
    except NoSuchElementException:
        pass
    except ElementNotVisibleException:
        pass

#通过该函数调用所有的点击功能,防止对话框干扰
def click_when_dialog(target_item):
    try:
        target_item.click()
    except WebDriverException:
        clickdialog()
        target_item.click()
    except NoSuchElementException, e:
        raise e
    except TimeoutException:
        time.sleep(2)
        target_item.click()
    # 处理某灵异bug……
    except WebDriverException:
        time.sleep(2)
        target_item.click()
    finally:
        time.sleep(4)


def get_one_kind_software(kind_name_selector, type_name):
    global software_name_list, software_url_list
    software_file = open(type_name + '.txt', 'w')
    kind_name = driver.find_element_by_xpath(kind_name_selector)
    click_when_dialog(kind_name)
    kind_id = kind_name.get_attribute('data-target')[1:]
    software_item = driver.find_elements_by_xpath('//*[@id="' + kind_id + '"]/div/ul/li')  # 目录下的软件列表项
    # 顺序点击每个目录项
    for i in range(0, len(software_item)):
        click_when_dialog(software_item[i])
        # 获取右侧数据
        # 多语言:
        # 获取多语言列表
        multi_languate_list = driver.find_elements_by_xpath('//*[@id="view_data_container"]/ul/li/a')
        if multi_languate_list:
            for j in range(0, len(multi_languate_list) - 1):
                software_id = 'lang_' + multi_languate_list[j].get_attribute('data-id')
                try:
                    click_when_dialog(multi_languate_list[j])
                except NoSuchElementException:
                    continue
                # 获取具体内容
                try:
                    software_name = driver.find_element_by_xpath('//*[@id="' + software_id + '"]/ul/li/div/label').text
                    software_url = driver.find_element_by_xpath(
                        '//*[@id="' + software_id + '"]/ul/li/div/label/input').get_attribute('data-url')
                except NoSuchElementException:
                    # 可能未加载完,但实际有
                    time.sleep(4)
                    try:
                        software_name = driver.find_element_by_xpath(
                            '//*[@id="' + software_id + '"]/ul/li/div/label').text
                        software_url = driver.find_element_by_xpath(
                            '//*[@id="' + software_id + '"]/ul/li/div/label/input').get_attribute('data-url')
                    # 这个真没有……
                    except NoSuchElementException:
                        continue
                software_name_list.append(software_name)
                software_url_list.append(software_url)
    for i in range(0, len(software_name_list)):
        software_file.write(software_name_list[i] + '    ' + software_url_list[i] + '\n')
    print type_name + '已爬取完成。\n'
    # 清空已写入至文件的列表
    software_name_list = []
    software_url_list = []
    software_file.close()


def file_to_excel():
    global count
    workbook = xlwt.Workbook()
    sheet_list = []
    for sheet_name in catalogue_name:
        sheet = workbook.add_sheet(unicode(sheet_name), cell_overwrite_ok=True)
        sheet_list.append(sheet)
    # 依次打开文件并写入对应的sheet
    for i in range(0, len(catalogue_name)):
        count = 0
        software_file = open(unicode(catalogue_name[i]) + '.txt', 'r')
        for line in software_file:
            sheet_list[i].write(count, 0, unicode(line.split('    ')[0]))
            sheet_list[i].write(count, 1, unicode(line.split('    ')[1]))
            count += 1
        software_file.close()
    print u'软件存储完成'
    workbook.save('software.xls')

driver = builddriver('Chrome')
if __name__ == "__main__":
    global driver
    driver.get('http://msdn.itellyou.cn/')
    time.sleep(5)
    for k in range(0, len(catalogue_list)):
        get_one_kind_software(catalogue_list[k], catalogue_name[k])
    driver.close()
    file_to_excel()


<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">此网站共分为8个目录,这段程序将每个目录的结果分别存为一个txt文件,最后再将其整理成一个excel文件。</span>

代码中的所谓“灵异bug”,是指在爬取操作系统那一目录时,经常到MS-DOS西班牙语之后会弹出异常:Other element would receive click:<nav class="balabala ……></nav>,且有时出现在这里,有时出现在爬取完Win7处,单步也抓不到。因此只能抛出一个WebdDriverException,处理方法是等2s后再试一次。

爬取后的成果:


个人感觉,用selenium+chromedriver/phantomjs爬取网页的效率实在是不高,但是碰到动态网页似乎又没有其他更好的办法。若有高人知道高效爬取动态网页的方法,还请不吝赐教。

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐