最近一直在琢磨爬虫,从最早的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爬取网页的效率实在是不高,但是碰到动态网页似乎又没有其他更好的办法。若有高人知道高效爬取动态网页的方法,还请不吝赐教。