站浏览量 站访问人数
目录
  1. 1. 正则提取
  2. 2. 代理配置
  3. 3. 连接数据库
  4. 4. 爬取图片
  5. 5. 爬取百度周边信息

python爬取网页信息,最简单的就是用BeautifulSoup等解析工具,解析出预定值,但是在具体实践中,依靠第三方的解析包远远不能达到目的,必须依赖正则方法,也是最基本的方法。

另外,爬取到信息后,需要保存,存储在文件、数据库等文件系统中。

爬虫,不能“裸奔”,以免被网站封住,所以,需要时长变换代理,以及ip等信息。

正则提取

正则的概念是模糊匹配,满足一定的预定格式,将被提取出或替换掉,以下是一个例子,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
原始数据:
data-original="http://ww3.sinaimg.cn/bmiddle/9150e4e5ly1fnty5s6wqzj205005mjrc.jpg" alt="吸猫女大佬"

目标:
提取出url和中文描述;

方案:
1,把需要提取的值去掉,用括号括起来;
2,针对提取的值进行正则编辑;
data-original="(.*?)" .*?alt="(.*?)"
因此,上述两个括号就是要提取的值;

接着是逻辑的编辑:
import re
reg = r'data-original="(.*?)" .*?alt="(.*?)"'
reg = re.compile(reg, re.S)#正则元组,正则有两个提取元素,两个括号匹配的,
result = re.findall(reg,respHtml)#返回数组
result就是元组数组,即找到的所有匹配。

代理配置

使用python3.5下的urllib来get网页的信息,如果网站没有做防爬虫措施,可以不用代理,但是,当做了防爬虫时,get信息时需要变换代理,并且调用的频率要限制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
设置多个代理,
user_agents = [
'Opera/9.25 (Windows NT 5.1; U; en)',
'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)',
'Mozilla/5.0 (compatible; Konqueror/3.5; Linux) KHTML/3.5.5 (like Gecko) (Kubuntu)',
'Lynx/2.8.5rel.1 libwww-FM/2.14 SSL-MM/1.4.1 GNUTLS/1.2.9',
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; AcooBrowser; .NET CLR 1.1.4322; .NET CLR 2.0.50727)",
"Mozilla/4.0 (compatible; MSIE 7.0; AOL 9.5; AOLBuild 4337.35; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)",
]

get访问网页信息时,随机设置代理,
headers = {}
headers['User-Agent']=random.choice(user_agents)
resp = urllib.request.Request(url=query,headers=headers)
respHtml = urllib.request.urlopen(resp).read().decode('utf-8')
注意headers=是一个dict,并且key是User-Agent,value才是代理的值。

连接数据库

爬取到数据后把数据保存进本地文件或者进数据库,

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
存储进本地文件,

line = 爬取到值串+'\n'
with open(filePath,'a',encoding='utf-8') as f:
f.write(line)
使用with open不需要close()操作,另外编码用utf-8,采用追加写模式;
------------------------------------------------------------------

存储数据进数据库,

import pymysql
构建连接实例,
db = pymysql.connect{
host = '',
port = '',
user = '',
password = '',
db = '',
charset='utf-8'
}

游标实例
cursor = db.cursor()

插入数据
cursor.execute("insert into image(`name`,`url`) values(
'{}','{}')".format(i[1],i[0]))
提交执行,
db.commit()
断开连接,在所有写完。
db.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

filePath = 'd:/spider_data_output.txt'

user_agents = [
'Opera/9.25 (Windows NT 5.1; U; en)',
'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)',
'Mozilla/5.0 (compatible; Konqueror/3.5; Linux) KHTML/3.5.5 (like Gecko) (Kubuntu)',
'Lynx/2.8.5rel.1 libwww-FM/2.14 SSL-MM/1.4.1 GNUTLS/1.2.9',
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; AcooBrowser; .NET CLR 1.1.4322; .NET CLR 2.0.50727)",
"Mozilla/4.0 (compatible; MSIE 7.0; AOL 9.5; AOLBuild 4337.35; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)",
]
def getImagesList(page=1):
query = 'http://www.doutula.com/photo/list/?page={}'.format(page)
headers = {}
headers['User-Agent']=random.choice(user_agents)
resp = urllib.request.Request(url=query,headers=headers)
respHtml = urllib.request.urlopen(resp).read().decode('utf-8')
respHtml = respHtml.replace('\r\n','').replace('\n','')
reg = r'data-original="(.*?)" .*?alt="(.*?)"'
reg = re.compile(reg, re.S)#正则元组,正则有两个提取元素,两个括号匹配的,
imageslist = re.findall(reg,respHtml)#返回数组
#数据进行保存
for i in imageslist:
# i=(url,name)
line = i[1]+'\t'+i[0]+'\n'
with open(filePath,'a',encoding='utf-8') as f:
f.write(line)


for i in range(1,1004):
print("正在爬取第{}页数据.".format(i))
time.sleep(2)
getImagesList(i)

爬取百度周边信息

百度地图开放的接口,可以获取多种信息,更多参考开发调用
,这里爬取的是给定百度的经纬度信息,获取周边指定半径内的地理信息,即搜索出周边的地点。
多线程爬取信息,存储在相应的文件中,

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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
#coding=utf-8

import urllib
import json
import time
import os
from bs4 import BeautifulSoup

noResultfilePath='****'

#所有的类别,每个小区周边的所有信息
allLabels = ['中餐厅','外国餐厅','小吃快餐店','蛋糕甜品店','咖啡厅','茶座','酒吧',
'星级酒店','快捷酒店','公寓式酒店',
'购物中心','超市','便利店','家居建材','家电数码','商铺','集市',
'通讯营业厅','邮局','物流公司','售票处','洗衣店','图文快印店','照相馆','房产中介机构','公用事业','维修点','家政服务','殡葬服务','彩票销售点','宠物服务','报刊亭','公共厕所',
'美容','美发','美甲','美体 ',
'公园','动物园','植物园','游乐园','博物馆','水族馆','海滨浴场','文物古迹','教堂','风景区',
'度假村','农家院','电影院','KTV','剧院','歌舞厅','网吧','游戏场所','洗浴按摩','休闲广场',
'体育场馆','极限运动场所','健身中心',
'高等院校','中学','小学','幼儿园','成人教育','亲子教育','特殊教育学校','留学中介机构','科研机构','培训机构','图书馆','科技馆',
'新闻出版','广播电视','艺术团体','美术馆','展览馆','文化宫',
'综合医院','专科医院','诊所','药店','体检机构','疗养院','急救中心','疾控中心',
'汽车销售','汽车维修','汽车美容','汽车配件','汽车租赁','汽车检测场',
'飞机场','火车站','地铁站','长途汽车站','公交车站','港口','停车场','加油加气站','服务区','收费站','桥',
'银行','ATM','信用社','投资理财','典当行',
'写字楼','住宅区','宿舍',
'公司','园区','农林园艺','厂矿',
'中央机构','各级政府','行政单位','公检法机构','涉外机构','党派团体','福利机构','政治教育机构'
]

'''
返回的值串:
百度返回的数据ListMap解析成Array[String]
'''
def getOneListMapToSting(listMap,label):
result = []
if listMap is not None:
for map in listMap:
location = map.get('location','{}')
lat = location.get('lat','0')
lng = location.get('lng','0')
uid = map.get('uid','default')
name = map.get('name','default')
address = map.get('address','default')
detail = map.get('detail','default')
street_id = map.get('street_id','0') #默认值
#组合结果输出
line = uid+'\t'+name+'\t'+street_id+'\t'+str(lat)+'\t'+str(lng)+'\t'+address+'\t'+str(detail)+'\t'+label
result.append(line)
#所有元素
return result


'''
array[String]写数据进表里,一个小区多条,即一个小区多行,写文件是追加的写,
f=open('路径/文件名', '读写格式', '编码方式', '错误处理方式')
'''

def appendWriteCommDataBase(arrayString,comm_id,filePath):
if arrayString is not None:
for oneData in arrayString:
writeLine = str(comm_id)+'\t'+oneData+'\r\n'
with open(filePath,'a',encoding='utf-8') as f:
f.write(writeLine)

'''
去除掉换行符,用在一行的最后
'''
def replacenNewlineChar(inputStr):
newStr = inputStr.replace('\r\n','').replace('\r','').replace('\n','')
return newStr

'''
读取小区的经纬度等信息,获取小区的附近数据点,
comm_id,lat,lng,queryWord

一个线程的操作,一份小区信息,结果文件,线程名字即读入的文件名字,

'''
def readCommLatLngGetBaiduMess(file,saveFile):

keys = ['ak=****']
key = keys[0]
akLen = len(keys)
useAKKEYID = 1
f = open(file,'r',encoding='utf-8')
line = f.readline()
vs = line.split('\t')
while 1:
if line is not None and len(vs)==4:
commId = replacenNewlineChar(vs[0])
lat = replacenNewlineChar(vs[2])
lng = replacenNewlineChar(vs[3])
print(file+'='+commId+'start')
#所有类别遍历查询
for label in allLabels:
listMap = getListMapMessByCommMess(commId,lat,lng,label,key)#获得信息
#判断获得的数据是否正确,即是否超量,
if (listMap=='error'):
print(label+'error')
elif (listMap=='302'):#判断key的使用是否超量
print('超限')
if (useAKKEYID<akLen):
key = keys[useAKKEYID]
useAKKEYID = useAKKEYID + 1#ak用的后移一个
else:
return 'error'
elif (listMap is None or len(listMap)<1):
print(label+' read mess is None')
else:
outResult = getOneListMapToSting(listMap,label)#返回信息格式处理
appendWriteCommDataBase(outResult,commId,saveFile)#结果写进保存文件中
time.sleep(0.001)
#下一个小区
print(file+'='+commId+'end')
line = f.readline()
vs = line.split('\t')
else:
break

f.close()
return 'ok'

'''
根据一个小区的经纬度信息,爬取其周边指定的标签数据
返回一个listMap

query=银行$酒店,查询地点关键字
http://lbsyun.baidu.com/index.php?title=lbscloud/poitags

tag=一级行业分类,二级行业分类,

'''
def getListMapMessByCommMess(commId,lat,lng,label,akkey):
#url查询
location='location='+str(lat)+','+str(lng)
queryPath = 'http://api.map.baidu.com/place/v2/search?radius=2000&output=json'
queryUrlCode = urllib.parse.quote(label) #编码url形式
query = queryPath+'&'+'query='+queryUrlCode+'&'+akkey+'&'+location
returnJson=json.loads('{}')
try:
resp = urllib.request.urlopen(query)
respHtml = resp.read().decode('utf-8')
resp.close()
# soup = BeautifulSoup(respHtml,from_encoding="utf-8")
# result = str(soup).replace('<html><head></head><body>','').replace('</body></html>','')
#判断返回的值是否正确,如果是限量则换一个key,如果是其他报错,则直接退出
returnJson = json.loads(respHtml)
if (returnJson.get('status',None)==302):
writeNoResultIntoTxt(commId,lat,lng,label)
return '302'
elif (returnJson.get('message',None)!='ok'):
writeNoResultIntoTxt(commId,lat,lng,label)
return 'error'

except urllib.error.URLError:
#url获取错误
time.sleep(0.2)#休息20秒
except Exception:
#其他异常
time.sleep(0.1)#休息20秒
#json串转成List[Map[]]
listMap = returnJson.get('results',None)
#如果没有获取到数据,则额外存储该小区,待后续再查
if listMap is None or len(listMap)<1:
writeNoResultIntoTxt(commId,lat,lng,label)
return listMap


'''
未找到的写进noresult-text里
'''
def writeNoResultIntoTxt(commId,lat,lng,label):
with open(noResultfilePath,'a',encoding='utf-8') as f:
f.write(str(commId)+'\t'+str(lat)+'\t'+str(lng)+'\t'+label+'\r\n')


#=====================================
'''
线程初始化,

'''
import threading

class myThread (threading.Thread):
def __init__(self, threadID, fromdirPath,todirPath,name):
threading.Thread.__init__(self)
self.threadID = threadID
self.name = name
self.fromdirPath = fromdirPath
self.todirPath = todirPath

def run(self):

getFileCommNearMessThread(self.fromdirPath,self.todirPath,self.name)

'''
线程的操作
每一份信息文件进行所有类别的爬取
'''
def getFileCommNearMessThread(fromdirPath,todirPath,threadName):
# vs = threadName.split(".")
saveFile = threadName+'_messout'
readCommLatLngGetBaiduMess(fromdirPath+'/'+threadName,todirPath+'/'+saveFile)

#-------------------------------------------------------------------
'''
开始主函数获取信息
'''
#1,小区的分信息目录
fromdirPath = '****'
todirPath = "****"
files = os.listdir(fromdirPath)#获取目录下的所有文件
print(files)

threadsList=[]
count = 1
for file in files:
t = myThread(count, fromdirPath,todirPath,file)
count = count + 1
threadsList.append(t)

for t in threadsList:
t.start()

for t in threadsList:
t.join()


#观察结果
print ("退出主线程")

这里需要注意的几个点:
1,查询的url编码是url的编码,即传入的中文串是%E8这种形式;
2,ak秘钥每天是有量限制的,需要多个ak;
3,json的解析,注意key不存在的情况,需要设置默认值;
4,url的get不是都能成功的,为了让程序继续执行,加入try;
5,没有成功的调用需要额外保存下来,待下回接着爬;