好久没写博客了,一直觉得之前写的都没啥技术性,这次分享个最近觉得很值得记录的一次操作吧~。
Leader临时分配给我个任务,要我爬取下政府采购网近一个月公开招标中二三甲医院的数据,这一下可把我难住了,要求还要用Python。
自己一直干的是Java开发,学校接触过的Python也是对图像进行一些简单的处理啥的,从来没有搞过爬虫,但是既然任务下来了,我肯定也想挑战下自己。
既然自己从来没有写过类似的功能,那就第一步就是打开Gayhub了,寻找前辈们写过的轮子了~,果不其然,Gayhub上真被我找到了一个关于政府采购网数据爬取的项目了https://github.com/Clemente420/ccgp_gov,此处忽略安装配饰Python环境。接下来就是一顿骚操作,git clone https://github.com/Clemente420/ccgp_gov.git
,将项目克隆下来,发现chromedriver
东东没见过,但是看着名字就知道是什么谷歌浏览器的驱动的了,于是乎就能才想到,应该是Python代码模拟打开谷歌浏览器,然后对网页标签进行分析记录了。配置chromedriver
见文章windows下配置chromedriver。废话不多说,上代码。
```python
# -*- coding: utf-8 -*-
import time
import pymysql
import xlrd
import xlutils.copy
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from multiprocessing.dummy import Pool as ThreadPool
from selenium.common.exceptions import NoSuchElementException
def connectDB():
host = "127.0.0.1"
dbName = "crwal_gov"
user = "root"
password = "123456"
# 此处添加charset='utf8'是为了在数据库中显示中文,此编码必须与数据库的编码一致
db = pymysql.connect(host, user, password, dbName, charset='utf8')
return db
cursorDB = db.cursor()
return cursorDB
def inserttable(item_name, budget_amount, admin_name, website_list):
insertContentSql = "INSERT INTO tj" + "(item_name,budget_amount,admin_name,website_list)VALUES(%s,%s,%s,%s)"
DB_insert = connectDB()
cursor_insert = DB_insert.cursor()
cursor_insert.execute(insertContentSql, (item_name, budget_amount, admin_name, website_list))
DB_insert.commit()
DB_insert.close()
def sub_crawler(website_list):
chrome_options = Options()
chrome_options.add_argument("--headless") # 无头chrome 不打开浏览器进行模拟点击
chrome_options.add_argument('--no-sandbox')
chrome_options.add_argument('--disable-dev-shm-usage')
driver = webdriver.Chrome(options=chrome_options)
driver.maximize_window()
driver.get(website_list)
time.sleep(0.5)
print(website_list) # 检错
try:
item_name = driver.find_element_by_xpath(
"//*[@id='detail']/div[2]/div/div[2]/div/div[2]/table/tbody/tr[2]/td[2]").text
budget_amount = driver.find_element_by_xpath(
"//*[@id='detail']/div[2]/div/div[2]/div/div[2]/table/tbody/tr[11]/td[2]").text
admin_name = driver.find_element_by_xpath(
"//*[@id='detail']/div[2]/div/div[2]/div/div[2]/table/tbody/tr[4]/td[2]").text
inserttable(item_name, budget_amount, admin_name, website_list)
print("已爬取{}\n".format(item_name))
except NoSuchElementException as msg:
print("已爬取{}\n{}\n".format("异常版面,需手动标注", website_list))
driver.quit()
def crawler(url, threadNum):
chrome_options = Options()
chrome_options.add_argument("--headless") # 无头chrome
chrome_options.add_argument('--no-sandbox')
chrome_options.add_argument('--disable-dev-shm-usage')
driver = webdriver.Chrome(options=chrome_options)
driver.maximize_window()
driver.get(url)
# 开始爬信息 (第一页)
website_list = [elemente.get_attribute("href") for elemente in
driver.find_elements_by_css_selector("a[style='line-height:18px']")]
time.sleep(0.8)
# 剩余页 爬信息
print(len(website_list)) # 爬到的信息条数
driver.quit()
pool = ThreadPool(processes=threadNum)
pool.map(sub_crawler, website_list)
pool.close()
pool.join()
if __name__ == "__main__":
page_num = 1
threadNum = 8
# url要事先把选项选好,还有页数记好
url1 = 'http://search.ccgp.gov.cn/bxsearch?searchtype=1&page_index='
url2 = '&bidSort=0&buyerName=&projectId=&pinMu=++0&bidType=1&dbselect=bidx&kw=系统&start_time=2020%3A04%3A27&end_time=2020%3A05%3A27&timeType=6&displayZone=&zoneId=&pppStatus=0&agentName='
for num in range(1, 6):
index = url1 + str(num) + url2
crawler(index, threadNum)
!!!!
接下来看骚操作
我这里的代码和参考github上的代码有点不同,而且要求的是爬取近一个月并且关键词是系统
的公开性招标。
原本作者采用的是保存的csv
文件中,但是没什么格式,我后来经过几番翻别人博客,然后将数据保存在Excle
中,这样就有了行列,数据格式就比价清晰了,但是代码一跑起来发现,每次数据保存都把上次保存的清空了,于是又找到个追加数据到Excle
中,这个时候,代码会先读取原本Excle
中已有的数据,然后拷贝一份,再把最新爬取分析到的数据追加到拷贝数据的后面,再把这个完整的数据保存到Excle
中。
上面似乎看起来已经没啥问题了,但是当开启多Excle
线程的时候,又出现了问题,线性同时运行,会造成抢占Excle
的冲突,这个时候又不行了,然后又对代码进行改造,改成单线程,但是又怕会消耗太多时间,通过分析爬取的列表地址栏,发现其实分成多个梯度进行爬取,然后将代码copy
多份,然后分别爬取不同页数的数据。
可以看到page_index
这个参数,然后这个参数对象对应着第几页,
这样我就每个Python
只爬取个十来页左右,然后存到不同的Excle里面,原本以为这样就可以了,顶多到时候将Excle
里面的数据进行合并,于是就开个远程下班回家了,但是!!!,回家到远程看到还是报错了,问题还是出在Excle
上,还是偶尔会出现打开文件然后对数据存储失败的情况。
既然还是不行,那就换一种思路,有没有可以不用涉及到前一个保存的记录,直接存就行了,这个时候就想到了数据库,于是,又打开搜索引擎,搜索如何使用Python将数据保存到数据库中
,经过查看几篇博客,于是再将保存数据到Excle
部分的代码又进行一番改造,将数据保存到MySQL
中,这个时候总不会出现文件读取的一系列的问题了吧。
事情总没有想象中的顺利,使用MySQL
也会出现报错,原因是Python模拟打开谷歌浏览器的时候,访问中国政府采购网相关招标信息详情的时候会出现网页打不开的情况,个人猜想有两个原因:1、网络不稳定,笔记本连的wifi可能出现波动等情况;2、开了很多个线程,导致网速还是反正不过来。第二天到公司在爬虫开启的同时,我打开浏览器确实访问很多页面也很慢,可能是模拟打开的网页太多网速吃不过来。但是还好之前已经分成很细的梯度进行爬取了,然后又对每个招标信息的详细网址进行记录了,这样的话,即使一个失败了,也可以重试,虽然会造成数据重复,但是由于保留了网址,每个详细信息网址是唯一的,后期去重就可以了。
终于等了快一个小时,总算全部爬完了。但是在筛选数据的时候又出现问题了,首先使用GROUP BY
然后在找出主键ID
不在GROUP BY
结果集里面的数据
原始数据
运行一看,发现结果居然是空,寻思一想问题应该是出在ID
上,或者是因为GROUP BY
和前面使用的IN
是同一张表的原因,于是我copy一份相同的表,然后再执行,发现还是不行o(╥﹏╥)o
果断打开搜索引擎,几番查找,又发现一个叫MIN()
的MySQL
函数,这样的话,在GROUP BY
重复的数据,就只会保存ID
最小的重复数据了,然后再直接用IN
,查出来的结果集直接create
一张表,存到最终的结果集,这样就基本完成了~。最后就根据要求该出的二三甲医院名单,筛选出最终符合要求的招标信息了。
最后需要的数据
前前后后花了差不多一天时间,虽然这次代码很简单,但是其中的思路转换我觉得很值得去体会,学习,通过这次数据爬取,数据筛选清洗,也学到了很多解决问题的思路。