Featured image of post 爬虫:selenium自动化框架爬取采招网的招标信息

爬虫:selenium自动化框架爬取采招网的招标信息

基于selenium自动化框架爬取招标信息,并制定客制化功能

老板要求做个招标信息采集工具,方便对过往招标成功案例进行分析,理论上开个会员就能批量将历年数据导出了,但是这个网站的会员好像很贵,而且我们也不需要那么多数据,光是它摆在明面的数据就足以分析一部分内容了,结果内容要导出到excel,那总的来说就是选一个爬虫工具,还有进行excel内容的输出。

爬虫工具选择

其实也没什么选择可以做,本科的时候用了selenium爬取过海岸线异步加载数据,然后经典的爬虫逻辑request+beautifulsoup解析网页结构,但是要爬取的这个网页,其实内容是表格异步加载,通常无法直接使用request获取整个页面的信息。那没法了,只能旧饭重炒。

selenium是基于浏览器内核的自动化框架,其实就是模拟人的操作去获取网页信息,这样如果人在打开网页的时候稍微停顿一下,就能获取表格内容的异步加载了,当然我只能想到这种异步爬取方法,其他更厉害的爬虫手段可能涉及非法爬虫,我也不太敢操作。这个自动化框架是模拟人的操作,所以一定程度上可以规避反爬虫手段。当然麻烦的点就是对应浏览器的驱动安装,首先必须要有浏览器,然后还要找到对应浏览器的内核驱动,这些安装方法其实在网上很多文章,这里也不赘述了。

待爬取网页的分析

网页地址是https://www.bidcenter.com.cn/,但是循路往前搜索发现,比如我要搜索“无人机”,其实主要信息集中在网址:

https://search.bidcenter.com.cn/search?keywords=%e6%97%a0%e4%ba%ba%e6%9c%ba

,这里的keywords=后面的乱码是中文转换的urlcode,当然这里也就提一嘴,实际在后文爬取中,利用浏览器内核会自动转换成urlcode,我一开始不知道还去查了一下如何转换为这种码。话说回来,对上面的这串主信息网址进行分析,发现其url规律很明显,主要是主页地址加上/search?,然后关联keywords,那这样的话,我们可以根据不同要求,在url中替换keywords就能实现对不同关键词的检索结果的请求。

进一步分析,通过chrome的检查工具,锁定我们所需要的表信息集中在<searchlist>中:

检查表格所在标签

利用xpath可以很轻易地获取这个标签下所有的text文本内容,这样我们复制该标签的xpath,然后利用selenium解析该xpath下的text文本:

复制xpath内容

代码编写方法如下:

1
2
3
4
5
6
7
8
from selenium import webdriver
from selenium.webdriver.chrome.options import Options


driver.get('https://search.bidcenter.com.cn/search?keywords=%e6%97%a0%e4%ba%ba%e6%9c%ba&diqu=20&type=4&mod=0')
driver.implicitly_wait(time_to_wait=5)
xpath = driver.find_element_by_xpath('//*[@id="searchListArea"]') 
tes=xpath.text.split('\n')

到这里,tes内就包含了该表的所有内容。接下来就是繁琐的内容解析归类输出到excel中,但是这个我想放在后文中一块叙述。

多页爬取的方法

上个内容中,我们只爬取了一页,但是实际网页中的内容,是非常多的,一页仅仅显示了40个中标结果,我们想要爬取第二页的内容咋办呢,打开浏览器第二页,观察url的变化,发现url多了一个关联标签page=2

https://search.bidcenter.com.cn/search?keywords=%E6%97%A0%E4%BA%BA%E6%9C%BA&mod=0&page=2

那方法就很明了了呀,利用python字符串串联的功能,定义两个变量,一个是搜索keywords的变量,一个是page的页数,就能制定自己程序开发时所输入的参数了。

整体爬取函数编写

这里程序的设计思路基本还是比较简单的,我先直接放代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
from selenium import webdriver
import time,csv
from selenium.webdriver.chrome.options import Options

chrome_options=Options()
chrome_options.add_argument('--headless')
browser = webdriver.Chrome(chrome_options=chrome_options)

def is_valid_date(strdate):
    '''判断是否是一个有效的日期字符串'''
    try:
        if ":" in strdate:
            time.strptime(strdate, "%Y-%m-%d %H:%M:%S")
        else:
            time.strptime(strdate, "%Y-%m-%d")
        return True
    except:
        return False
    
    
def crab(search_str, save_csv_path, pages):
    start = time.time()
    dic = {'中标结果': None, '地区': None, '中标金额': None, '中标时间': None , '页数': None}
    header = list(dic.keys())
    with open(save_csv_path,"a", newline='', encoding='utf-8-sig') as csvfile:
        writer = csv.DictWriter(csvfile, fieldnames=header) 
        writer.writeheader()  
        csvtest = {}
        driver = webdriver.Chrome()
        if int(pages)==1:
            csvtest['页数'] = int(pages)
            driver.get('https://search.bidcenter.com.cn/search?keywords=' + search_str + '&diqu=20&type=4&mod=0')
            driver.implicitly_wait(time_to_wait=5)
            xpath = driver.find_element_by_xpath('//*[@id="searchListArea"]') 
            tes=xpath.text.split('\n')
            for i in range(0,len(tes)-1):
                if tes[i][:4]=='中标结果':
                    csvtest['中标结果'] = tes[i][5:]
                elif tes[i][:4]=='中标金额':
                    if len(tes[i])<16:
                        csvtest['中标金额'] = tes[i][5:]
                    else:
                        continue
                elif tes[i][:2]=='广西':
                    csvtest['地区'] = tes[i]
                elif is_valid_date(tes[i]):
                    csvtest['中标时间'] = tes[i]
                    writer.writerow(csvtest)
        elif int(pages)>1:
            driver.implicitly_wait(pages*5)
            for j in range(1, int(pages)+1):
                csvtest['页数'] = j
                driver.get('https://search.bidcenter.com.cn/search?keywords=' + search_str + '&diqu=20&type=4&mod=0&page='+str(j))
                xpath = driver.find_element_by_xpath('//*[@id="searchListArea"]') 
                tes=xpath.text.split('\n')
                for k in range(0,len(tes)-1):
                    if tes[k][:4]=='中标结果':
                        csvtest['中标结果'] = tes[k][5:]    
                    elif tes[k][:4]=='中标金额':
                        if len(tes[k])<16:
                            csvtest['中标金额'] = tes[k][5:]
                        else:
                            continue
                    elif tes[k][:2]=='广西':
                        csvtest['地区'] = tes[k]
                    elif is_valid_date(tes[k]):
                        csvtest['中标时间'] = tes[k]
                        writer.writerow(csvtest)
                time.sleep(4)
    end = time.time()
    browser.close()
    browser.quit()  
    return end-start  


if __name__ == '__main__':
    print(crab('无人机', 'testwe3.csv', 20))
    

上述代码中,

1
2
3
chrome_options=Options()
chrome_options.add_argument('--headless')
browser = webdriver.Chrome(chrome_options=chrome_options)

三行主要是设置运行时不显示浏览器具体操作,这里注释掉的话可以看浏览器自动化操作的过程。

is_valid_date(strdate)函数主要对日期进行处理,为了判断和正则化日期并保存到excel中,这个函数还是很有必要的。

主函数是crab(),需要的参数在上文中阅读过来的话,应该都很明了了,主要就是关键词和页数,还有csv保存的地方。在selenium解析xpath下的text后,就是多重if判断进行内容的解析,整体程序设计完成。

程序界面设计

界面的设计仿造之前的敏捷开发案例,直接修改其中几个控件就能使用,在这里还扩展了一些内容,可以从固定某页爬取到某页,而不是只能从第一页开始爬,比较有趣的地方是这次爬取到csv的信息,爬取完成后程序会帮使用者打开该文件,主要是利用os包完成。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
def gui():
    
    sg.theme('LightGrey1') 
    layout = [
            [sg.Text('爬取关键词:',font=("宋体", 11)), sg.Input('请输入需要爬取的关键词,如“无人机”', font=("宋体", 11), size=50, key='crabekeyword')],
                [sg.Text('CSV保存路径:',font=("宋体", 11)), sg.Input('输入后程序会自行创建,需要带有拓展名‘.csv’', font=("宋体", 11), key='csvpath', size=49)],
                   [ sg.Text('自定义爬取页:',font=("宋体", 11)), sg.Input('请输入第一页', font=("宋体", 11), key='startnum', size=22), sg.Input('请输入最后一页', font=("宋体", 11), key='endnum', size=22)],
            [ sg.Text('睡眠时间:',font=("宋体", 11)), sg.Input('如果发现缺少页数,可以适当提高该参数,一般为‘5’', font=("宋体", 11), key='sleeptime', size=52)],
            
            [sg.Text('程序运行记录',justification='center')],
            [sg.Output(size=(70, 20),font=("宋体", 10))],
            [sg.Button('开始爬取')]
            
            ]    
    
    window = sg.Window('广西招标情况爬取工具', layout, font=("宋体", 15), default_element_size=(50,1))
    
    
    while True:
        event, values = window.read()
        if event in (None, '关闭'):   # 如果用户关闭窗口或点击`关闭`
            break
        if event == '开始爬取':
            print("程序运行完成以后,会自动打开文件,请稍等")
            print("总花费时间  " , foo(values['crabekeyword'], values['csvpath'], values['startnum'], values['endnum'], values['sleeptime']))
            os.system("explorer.exe %s" % values['csvpath'])  
            
                   
    window.close()

整体代码

这里给出程序设计的总体代码:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113

import os,time,csv
import PySimpleGUI as sg    
from selenium import webdriver
from selenium.webdriver.chrome.options import Options



def is_valid_date(strdate):
    '''判断是否是一个有效的日期字符串'''
    try:
        if ":" in strdate:
            time.strptime(strdate, "%Y-%m-%d %H:%M:%S")
        else:
            time.strptime(strdate, "%Y-%m-%d")
        return True
    except:
        return False


    
def foo(search_str, save_csv_path, startpage, endpage, sleeptime):
    chrome_options=Options()
    chrome_options.headless = True
    driver = webdriver.Chrome(options=chrome_options)    

    start = time.time()
    dic = {'中标结果': None, '地区': None, '中标金额': None, '中标时间': None , '页数': None}
    header = list(dic.keys())
    with open(save_csv_path,"a", newline='', encoding='utf-8-sig') as csvfile:
        writer = csv.DictWriter(csvfile, fieldnames=header) 
        writer.writeheader()  
        csvtest = {}
        
        if int(startpage)==1 and int(endpage)==1:
            csvtest['页数'] = int(startpage)
            driver.get('https://search.bidcenter.com.cn/search?keywords=' + search_str + '&diqu=20&type=4&mod=0')
            driver.implicitly_wait(time_to_wait=5)
            xpath = driver.find_element_by_xpath('//*[@id="searchListArea"]') 
            tes=xpath.text.split('\n')
            for i in range(0,len(tes)-1):
                if tes[i][:4]=='中标结果':
                    csvtest['中标结果'] = tes[i][5:]
                elif tes[i][:4]=='中标金额':
                    if len(tes[i])<16:
                        csvtest['中标金额'] = tes[i][5:]
                    else:
                        continue
                elif tes[i][:2]=='广西':
                    csvtest['地区'] = tes[i]
                elif is_valid_date(tes[i]):
                    csvtest['中标时间'] = tes[i]
                    writer.writerow(csvtest)
        elif int(endpage)>1 or int(endpage)==int(startpage):
            driver.implicitly_wait(endpage*5)
            for j in range(int(startpage), int(endpage)+1):
                csvtest['页数'] = j
                driver.get('https://search.bidcenter.com.cn/search?keywords=' + search_str + '&diqu=20&type=4&mod=0&page='+str(j))
                time.sleep(int(sleeptime))
                xpath = driver.find_element_by_xpath('//*[@id="searchListArea"]') 
                tes=xpath.text.split('\n')
                
                
                for k in range(0,len(tes)-1):
                    if tes[k][:4]=='中标结果':
                        csvtest['中标结果'] = tes[k][5:]    
                    elif tes[k][:4]=='中标金额':
                        if len(tes[k])<16:
                            csvtest['中标金额'] = tes[k][5:]
                        else:
                            continue
                    elif tes[k][:2]=='广西':
                        csvtest['地区'] = tes[k]
                    elif is_valid_date(tes[k]):
                        csvtest['中标时间'] = tes[k]
                        writer.writerow(csvtest)
                
    end = time.time()
    driver.close()
    driver.quit()  
    return end-start  


def gui():
    
    sg.theme('LightGrey1') 
    layout = [
            [sg.Text('爬取关键词:',font=("宋体", 11)), sg.Input('请输入需要爬取的关键词,如“无人机”', font=("宋体", 11), size=50, key='crabekeyword')],
                [sg.Text('CSV保存路径:',font=("宋体", 11)), sg.Input('输入后程序会自行创建,需要带有拓展名‘.csv’', font=("宋体", 11), key='csvpath', size=49)],
                   [ sg.Text('自定义爬取页:',font=("宋体", 11)), sg.Input('请输入第一页', font=("宋体", 11), key='startnum', size=22), sg.Input('请输入最后一页', font=("宋体", 11), key='endnum', size=22)],
            [ sg.Text('睡眠时间:',font=("宋体", 11)), sg.Input('如果发现缺少页数,可以适当提高该参数,一般为‘5’', font=("宋体", 11), key='sleeptime', size=52)],
            
            [sg.Text('程序运行记录',justification='center')],
            [sg.Output(size=(70, 20),font=("宋体", 10))],
            [sg.Button('开始爬取')]
            
            ]    
    
    window = sg.Window('广西招标情况爬取工具', layout, font=("宋体", 15), default_element_size=(50,1))
    
    
    while True:
        event, values = window.read()
        if event in (None, '关闭'):   # 如果用户关闭窗口或点击`关闭`
            break
        if event == '开始爬取':
            print("程序运行完成以后,会自动打开文件,请稍等")
            print("总花费时间  " , foo(values['crabekeyword'], values['csvpath'], values['startnum'], values['endnum'], values['sleeptime']))
            os.system("explorer.exe %s" % values['csvpath'])  
            
                   
    window.close()
gui()

利用pyinstaller打包以后,程序文件执行结果如下: 运行界面

总结

selenium爬虫是我本科毕业后第二次接触,但是它本身并不难用,因为是模拟人的浏览器行为,所以很容易能够绕过一些反爬虫手段如异步加载(这我自己也不知道算不算反爬虫),但是如果是用在生产环境的爬虫,其实整体程序环境的布置就很复杂,也是和之前的文章一样,为了方便甲方布置环境是不是得写个.bat?高低都得要。而且为了完整加载页面,这里利用了睡眠机制time.sleep(),人为加长了爬虫时间,那这样超大数据的爬取,比如爬个1000页,那不得几个小时过后了。因此要是真的投入到别的生产环境还是得研究异步加载的其他爬取手段。当然现在这个程序目前也够用,在当下看起来也够高效,那这活也算结束了。

Author by Jerrychoices
Built with Hugo
主题 StackJimmy 设计

本站访客数人次 总访问量 本文阅读量