接上一部分:https://blog.csdn.net/qq_30613521/article/details/103758609
part three
上一部分讲到pyqt5界面部分,收集用户输入信息,并开启爬虫进程。
这一部分主要讲scrapy爬虫,是主要核心功能,就是爬取数据,前面的PyQt5其实就是相当于给scrapy充值买个皮肤,不用可视化界面其实直接在代码里通过配置文件写入用户选择的信息也可以的。但是我们是注重用户体验的,不注重用户体验,人家怎么乐意充钱呢(。。。黑一下麻花疼)^_^。
书归正传:先列提纲分解。scrapy 基础资料很多,如果没学过scrapy 建议先查资料,直接看这个会懵逼。
① scrapy 拿到pyqt5界面传递的用户输入参数,发送请求获取数据
② 将请求数据封装到item 中
③ 在管道内将数据写入本地excel
④ 将返回的所有数据发给matplotlib处理线程统计,并将统计的结果处理成图像化数据发给matplotlib展示。
scrapy 官方文档:https://scrapy-chs.readthedocs.io/zh_CN/1.0/intro/overview.html
session one: scrapy 根据用户输入发送请求。
回忆一下parttwo中使用脚本启动scrapy时给spider传递了几个值:
process.crawl(VersionSpider, param=message, logrecord=queue,signal=monitor)
param 是用户输入的信息,logrecord是用于获取scrapy中数据日志队列,signal是matplotlib通信队列(暂时不讲了,2.0版本再讲)
既然界面人家给传了参数,在爬虫里就要拿到数据,spider中初始化init拿到用户输入数据(param)2个队列(logrecord、signal)
class VersionSpider(scrapy.Spider):
name = 'versionSpider'
start_urls = ['http://api.map.baidu.com']
param = None
url_list = ''
def __init__(self, param, logrecord, signal):
self.param = param
self.logrecord = logrecord
self.summaryItems = []
self.initData(param)
keyword = param['keyWord']
provinceList = param['province']
filePath = param['filePath']
baiduAk = param['baiduAk']
接下来开始发请求,并携带用户输入的参数,向度娘请求数据,看partone百度地图api接口文档。看度娘的牌坊咋写的(既当bz又立pf,O(∩_∩)O哈哈~):
http://api.map.baidu.com/place/v2/search?query=ATM机&tag=银行®ion=北京&output=json&ak=您的ak //GET请求
就是说你要告诉度娘,你要搜索的关键字query=?比如“天上人间”,地点region=“北京三元桥亮马河路边黄牌子” 您的手牌ak='男宾28号一位',首先要按度娘的要求整理出度娘的请帖:
城市:我已经把所有的省份包含的城市写成了一个字典
cityDict = {
'黑龙江': ['哈尔滨市', '黑河市', '伊春市', '齐齐哈尔市', '鹤岗市', '佳木斯市', '双鸭山市', '绥化市', '大庆市', '七台河市', '鸡西市', '牡丹江市', '大兴安岭'],
'吉林': ['长春市', '白城市', '松原市', '吉林市', '四平市', '辽源市', '白山市', '通化市', '延边朝鲜族自治州'],
'辽宁': ['沈阳市', '铁岭市', '阜新市', '抚顺市', '朝阳市', '本溪市', '辽阳市', '鞍山市', '盘锦市', '锦州市', '葫芦岛市', '营口市', '丹东市', '大连市'],
'河北': ['石家庄市', '张家口市', '承德市', '唐山市', '秦皇岛市', '廊坊市', '保定市', '沧州市', '衡水市', '邢台市', '邯郸市'],
'山西': ['太原市', '大同市', '朔州市', '忻州市', '阳泉市', '晋中市', '吕梁市', '长治市', '临汾市', '晋城市', '运城市'],
'内蒙古': ['呼和浩特市', '呼伦贝尔市', '通辽市', '赤峰市', '巴彦淖尔市', '乌兰察布市', '包头市', '鄂尔多斯市', '乌海市', '兴安盟', '锡林郭勒盟', '阿拉善盟'],
'河南': ['郑州市', '安阳市', '鹤壁市', '濮阳市', '新乡市', '焦作市', '三门峡市', '开封市', '洛阳市', '商丘市', '许昌市', '平顶山市', '周口市', '漯河市',
'南阳市', '驻马店市', '信阳市'],
'湖北': ['武汉市', '十堰市', '襄阳市', '随州市', '荆门市', '孝感市', '宜昌市', '黄冈市', '鄂州市', '荆州市', '黄石市', '咸宁市', '恩施', '仙桃市', '潜江市',
'天门市', '神农架'],
'湖南': ['长沙市', '岳阳市', '张家界市', '常德市', '益阳市', '湘潭市', '株洲市', '娄底市', '怀化市', '邵阳市', '衡阳市', '永州市', '郴州市',
'湘西土家族苗族自治州'],
'广东': ['广州市', '韶关市', '梅州市', '河源市', '清远市', '潮州市', '揭阳市', '汕头市', '肇庆市', '惠州市', '佛山市', '东莞市', '云浮市', '汕尾市', '江门市',
'中山市', '深圳市', '珠海市', '阳江市', '茂名市', '湛江市'],
'广西': ['南宁市', '桂林市', '河池市', '贺州市', '柳州市', '百色市', '来宾市', '梧州市', '贵港市', '玉林市', '崇左市', '钦州市', '防城港市', '北海市'],
'海南': ['海口市', '三亚市'],
'江苏': ['南京市', '连云港市', '徐州市', '宿迁市', '淮安市', '盐城市', '泰州市', '扬州市', '镇江市', '南通市', '常州市', '无锡市', '苏州市'],
'浙江': ['杭州市', '湖州市', '嘉兴市', '绍兴市', '舟山市', '宁波市', '金华市', '衢州市', '台州市', '丽水市', '温州市'],
'安徽': ['合肥市', '淮北市', '亳州市', '宿州市', '蚌埠市', '阜阳市', '淮南市', '滁州市', '六安市', '马鞍山市', '巢湖市', '芜湖市', '宣城市', '铜陵市', '池州市',
'安庆市', '黄山市'],
'福建': ['福州市', '宁德市', '南平市', '三明市', '莆田市', '龙岩市', '泉州市', '漳州市', '厦门市'],
'江西': ['南昌市', '九江市', '景德镇市', '上饶市', '鹰潭市', '抚州市', '新余市', '宜春市', '萍乡市', '吉安市', '赣州市'],
'山东': ['济南市', '德州市', '滨州市', '东营市', '烟台市', '威海市', '淄博市', '潍坊市', '聊城市', '泰安市', '莱芜市', '青岛市', '日照市', '济宁市', '菏泽市',
'临沂市', '枣庄市'],
'四川': ['成都市', '广元市', '巴中市', '绵阳市', '德阳市', '达州市', '南充市', '遂宁市', '广安市', '资阳市', '眉山市', '雅安市', '内江市', '乐山市', '自贡市',
'泸州市', '宜宾市', '攀枝花市', '阿坝藏族羌族自治州', '甘孜藏族自治州', '凉山彝族自治州'],
'贵州': ['贵阳市', '遵义市', '六盘水市', '安顺市', '铜仁', '黔西南布依族苗族自治州', '毕节', '黔东南苗族侗族自治州', '黔南布依族苗族自治州'],
'云南': ['昆明市', '昭通市', '丽江市', '曲靖市', '保山市', '玉溪市', '临沧市', '普洱市', '楚雄彝族自治州', '红河哈尼族彝族自治州', '文山壮族苗族自治州',
'西双版纳傣族自治州', '大理白族自治州', '德宏傣族景颇族自治州', '怒江傈僳族自治州', '迪庆藏族自治州'],
'西藏': ['拉萨市', '昌都', '山南', '日喀则', '那曲', '阿里', '林芝'],
'陕西': ['西安市', '榆林市', '延安市', '铜川市', '渭南市', '宝鸡市', '咸阳市', '商洛市', '汉中市', '安康市'],
'甘肃': ['兰州市', '嘉峪关市', '酒泉市', '张掖市', '金昌市', '武威市', '白银市', '庆阳市', '平凉市', '定西市', '天水市', '陇南市', '临夏回族自治州',
'甘南藏族自治州'],
'青海': ['西宁市', '海东', '海北藏族自治州', '黄南藏族自治州', '海南藏族自治州', '果洛藏族自治州', '玉树藏族自治州', '海西蒙古族藏族自治州'],
'宁夏': ['银川市', '石嘴山市', '吴忠市', '中卫市', '固原市'],
'新疆': ['乌鲁木齐市', '克拉玛依市', '吐鲁番', '哈密', '昌吉', '博尔塔拉蒙古自治州', '巴音郭楞蒙古自治州', '阿克苏地区', '克孜勒苏柯尔克孜自治州', '喀什', '和田',
'伊犁哈萨克自治州', '塔城', '阿勒泰', '石河子市', '阿拉尔市', '图木舒克市', '五家渠市'],
'北京': ['北京市'],
'上海': ['上海市'],
'天津': ['天津市'],
'重庆': ['重庆市']
}
剩下的事就交给python吧,根据页面传递过来的用户输入信息所选择的身份,遍历字典找到所选省份的所有地级市,然后开始按度娘要求写通关文牒。走万花楼取经去。。。
def dealUrl(city, keyword, accessKey):
url = 'http://api.map.baidu.com/place/v2/search?query=' + keyword + '®ion=' + city + '&output=json&page_size=20&page_num=0&ak=' + accessKey
url = quote(url, safe=string.printable)
return url
class VersionSpider(scrapy.Spider):
name = 'versionSpider'
start_urls = ['http://api.map.baidu.com']
param = None
url_list = ''
def __init__(self, param, logrecord, signal):
self.param = param
self.logrecord = logrecord
self.summaryItems = []
self.initData(param)
keyword = param['keyWord']
provinceList = param['province']
filePath = param['filePath']
baiduAk = param['baiduAk']
# 设置全局变量方便管道中获取数据
setGlobalConfig('keyWord', keyword)
setGlobalConfig('province', provinceList)
setGlobalConfig('filePath', filePath)
setGlobalConfig('baiduAK', baiduAk)
setGlobalConfig('logrecord', self.logrecord)
self.monitorThread = MonitorThread(signal, self)
self.monitorThread.start()
def initData(self,param):
provinceList = param['province']
AllProDict = getCityList()
selectProDict = {}
for selectPro in provinceList:
if selectPro in AllProDict:
selectProDict[selectPro] = AllProDict[selectPro]
self.selectProvinceData = selectProDict
def parse(self, response):
param = self.param
keyword = param['keyWord']
filePath = param['filePath']
acessKey = param['baiduAk']
# 算出总城市数用于进度条计算
for key in self.selectProvinceData:
for city in self.selectProvinceData[key]:
url = dealUrl(city, keyword, acessKey)
searchInfo = {}
searchInfo['keyword'] = keyword
searchInfo['city'] = city
searchInfo['province'] = key
searchInfo['filePath'] = filePath
searchInfo['accessKey'] = acessKey
yield Request(url=url, meta=searchInfo, callback=self.parse_resData)
# 第一部分拿数据
def parse_resData(self, response):
keyword = response.meta['keyword']
city = response.meta['city']
accessKey = response.meta['accessKey']
allData = dealbaiduMapRes(response.body.decode("utf-8"))
isDataRight = checkDataRight(allData)
if isDataRight[0]:
for info in isDataRight[1]:
item = dealItems(info)
self.logrecord.put(str(item))
self.summaryItems.append(item)
yield item
if (allData['total'] > 20):
pages = math.ceil(allData['total'] / 20) + 1
for page in range(2, pages):
url = dealUrlPart2(city, keyword, page, accessKey)
yield Request(url=url, meta=response.meta, callback=self.parse_secondPart)
# 第二部分拿数据
def parse_secondPart(self, response):
allData = dealbaiduMapRes(response.body.decode("utf-8"))
isDataRight = checkDataRight(allData)
if isDataRight[0]:
for info in isDataRight[1]:
item = dealItems(info)
self.logrecord.put(str(item))
self.summaryItems.append(item)
yield item
# 打开处理数据线程,10s取一次主爬虫数据并处理成matplotlib可接受的数据
def openMonitor(self,province):
self.monitorDataThread = MonitorData(province[0], self.selectProvinceData, self.summaryItems, self)
self.monitorDataThread.start()
scrapy 的parse方法用于处理返回的请求数据,在response中,度娘给的数据格式是:
json 数据很好处理了,用json包转换为json对象
def dealbaiduMapRes(info):
return json.loads(info)
有的时候你发的参数不对,或者度娘来大姨妈了,会给你一串空数据需要特殊处理一下
def checkDataRight(info) :
compareCount = 0
filterList = []
if type(info['results']) is list and len(info['results']) > 0:
for item in info['results']:
if 'address' in item:
# print("有地址属性")
compareCount = compareCount + 1
filterList.append(item)
else:
print("没有地址属性")
if compareCount > 0:
return (True,filterList)
else:
return (False,[])
else:
return (False,[])
session two 将返回的数据封装到item中
处理完异常之后需要封装到item 中,先定义item :item中都是你需要的字段(名字,地址,电话,省,市,区,坐标等)
class VisionItem(scrapy.Item):
id = scrapy.Field()
item_name = scrapy.Field()
address = scrapy.Field()
tel = scrapy.Field()
province = scrapy.Field()
city = scrapy.Field()
area = scrapy.Field()
x = scrapy.Field()
y = scrapy.Field()
primaryId = scrapy.Field()
session three 将管道中的数据写入到excel中
具体封装就不说了,直接yield item 给管道,管道中拿到item数据写入excel:
class VisionPipeline(object):
def __init__(self):
self.wb = Workbook()
self.ws = self.wb.active
self.ws.append(['province','city','area','item_name','address','tel','x','y','primaryId'])
self.path = getGlobalConfig('filePath')
self.wb.save(self.path + '/data.xlsx')
self.dataList = []
def process_item(self, item, spider):
line = [item['province'], item['city'], item['area'], item['item_name'], item['address'], item['tel'],
item['x'], item['y'], item['primaryId']]
self.dataList.append(line)
if len(self.dataList) > 1000:
self.saveToExcel()
return item
def saveToExcel(self):
datalist = pd.read_excel(self.path + '/data.xlsx', header=None, index_label=False)
normallist = nm.array(datalist).tolist()
concatList = nm.vstack((normallist, self.dataList))
df = pd.DataFrame(concatList)
writer = pd.ExcelWriter(self.path + "/data.xlsx")
df.to_excel(writer, sheet_name='sheet1', header=None, index_label=False, index=False)
writer.save()
self.dataList.clear()
def close_spider(self, spider):
self.saveToExcel()
getGlobalConfig('logrecord').put('<div style="color: red">爬取结束</div>')
具体写入excel,使用的是openxyl包,性能一般,所以我采用了批量写入,攒够了1000条在写入excel ,写入excel的路径其实是从全局变量中拿到的,刚开始爬虫初始化的时候就将路径赋值给全局变量了,现在从全局变量中取的路径,在按照路径写入excel。具体openxyl的使用方法不具体细说了,掘金,简书,csdn很多资料。
session four matplotlib 1.0版本暂时不说了,后面弄好了放开再补上
part three至此完结了。。。于2019年12月31日 ,跨年夜。。。
part four 涉及matplotlib 2.0版本功能,稍迟更新
1.0版代码:
链接:https://pan.baidu.com/s/1ciSKHndU7FD8-EHpOu9sPg
提取码:u0mi
说明:main.py引用了很多无效的包是为了给pyinstaller打包用
感谢支持!