最近要做一些数据爬取的工作,需要拿一些地理位置数据的信息,自然而然先使用百度的开放api来作为爬虫的获取对象。自己写了个保存数据到mongodb数据库的python爬虫,放在博客上记录一下。
先放全局变量的部分,是一些必要信息:
import requests
import json
import pymongo
from time import sleep
import threading
#参数
# query=药店 想查询的内容
# region=北京 想查询的城市
# page_num={} 页码, 意思是第几页
# page_size=20 每页显示的个数
# ak=你的百度地图api授权码
queries = ["客栈", "酒店", "旅馆", "民宿", "宾馆"]
cities = ["北京","上海","广州"]
ak = "百度地图api授权码"
base_url = 'http://api.map.baidu.com/place/v2/search?query={}®ion={}&output=json&output=json&ak={}&page_num={}&page_size=50&scope=2'
#连接mongo
client = pymongo.MongoClient(host='localhost', port=27017)
db = client.hotels
collection = db.hotel
因为要获取酒店相关信息,而百度的查询工具是关键词查询,所以相关的民宿、旅店也要放进去(百度不支持多参数)作为一个列表循环查找。base_url作为模板url,各个参数可以在官方文档中查到。
在这里使用了mongodb数据库作为输出源,插件用pymongo。
接下来是主要函数部分:
def getUrl(query, city, i):
url = base_url.format(query, city, ak, i)
return url
def getData(url):
response = requests.get(url) # 对网页进行请求
jsondata = json.loads(response.text) # 对下载的内容进行loads
for j in jsondata['results']:
#print(j,'\n')
result = {
'name': j.get('name'),
'tag': j['detail_info'].get('tag'),
'lat': j['location'].get('lat'),
'lng': j['location'].get('lng'),
'address': j.get('address'),
'province': j.get('province'),
'city': j.get('city'),
'area': j.get('area'),
'tel': j.get('telephone') if j.get('telephone') else 0,
'score': j['detail_info'].get('overall_rating') if j['detail_info'].get('overall_rating') else 0
}
print(result, '\n')
collection.update({'name': result['name']}, {
'$set': result}, True) # 数据库去重
return jsondata["results"]
为了后面的多线程调用的更方便点,把getUrl和getData拆开写了,百度api单个网页上有一定数量的json格式数据,因此先循环获得多个url,再用getData拿到详细数据。
result是最后获得的数据格式,collection.update而不是insert是为了完成数据的去重,这里是用数据的name字段来判断数据库中是否已含有该数据条,用set形式保存。
接下来就是把代码打包进多线程中,这里写了一个run函数来引用线程:
def run(city,query):
for i in range(0, 10): # 循环请求
url = getUrl(query, city, i)
t = threading.Thread(target=getData, args=(url,)) #加入了多线程
t.setDaemon(True)
t.start()
if not getData(url):
break
#print("========================",res,"========================")
sleep(1) # 防止被阻止访问,sleep一下
t.join()
setDaemon(True) 可以提供一个守护线程,因为轮询了10页的数据,而很多情况下数据只出现在前7页,所以需要在获取到空数据时把当前线程取消,免得有源源不断的新线程。。。
最后放上完整代码:
import requests
import json
import pymongo
from time import sleep
import threading
#参数
# query=药店 想查询的内容
# region=北京 想查询的城市
# page_num={} 页码, 意思是第几页
# page_size=20 每页显示的个数
# ak=你的百度地图api授权码
queries = ["客栈", "酒店", "旅馆", "民宿", "宾馆"]
cities = ["北京","上海","广州"]
ak = ""
base_url = 'http://api.map.baidu.com/place/v2/search?query={}®ion={}&output=json&output=json&ak={}&page_num={}&page_size=50&scope=2'
#连接mongo
client = pymongo.MongoClient(host='localhost', port=27017)
db = client.hotels
collection = db.hotel
def getUrl(query, city, i):
url = base_url.format(query, city, ak, i)
return url
def getData(url):
response = requests.get(url) # 对网页进行请求
jsondata = json.loads(response.text) # 对下载的内容进行loads
for j in jsondata['results']:
#print(j,'\n')
result = {
'name': j.get('name'),
'tag': j['detail_info'].get('tag'),
'lat': j['location'].get('lat'),
'lng': j['location'].get('lng'),
'address': j.get('address'),
'province': j.get('province'),
'city': j.get('city'),
'area': j.get('area'),
'tel': j.get('telephone') if j.get('telephone') else 0,
'score': j['detail_info'].get('overall_rating') if j['detail_info'].get('overall_rating') else 0
}
print(result, '\n')
collection.update({'name': result['name']}, {
'$set': result}, True) # 数据库去重
return jsondata["results"]
def run(city,query):
for i in range(0, 10): # 循环请求
url = getUrl(query, city, i)
t = threading.Thread(target=getData, args=(url,)) #加入了多线程
t.setDaemon(True)
t.start()
if not getData(url):
break
#print("========================",res,"========================")
sleep(1) # 防止被阻止访问,sleep一下
t.join()
if __name__ == "__main__":
#页数i
for city in cities:
for query in queries:
run(city,query)
client.close()
本身百度api会有访问限制,普通用户每5秒都有获取数据的限制,没有用多线程时不得不每次都sleep(5)。。。改用多线程后就非常快啦