Python爬虫实战
文章目录
一、写在前面
在学习了一天的Python基础语法之后,本着“学中干,干中学”的思想,我开始了菜鸡的爬虫生活
爬虫网站
感谢图吧给我这次练手机会
https://poi.mapbar.com/beijing/
选择图吧的原因如下:
- 没有反爬机制
- 整体结构清晰统一,可重复爬取
- 贾老师要求的(嘿嘿)
目的&思路
目的:获取北京市旅游、酒店、交通等类别的具体信息及商家个人页链接
思路:先从北京市页面爬取各个类别下的所有的分类名和链接,然后循环访问每一类,爬取所有商家信息,最后整合成json格式文件(jl也可)
工具:Requests获取数据,lxml分析数据
依赖库
- lxml
- requests
- json
二、具体流程
各大版块分类抓取
抓取方法请看Xpath定位
url = "https://poi.mapbar.com/beijing/"
response = requests.get(url)
txt = response.text
html = etree.HTML(txt)
result = html.xpath('//div[@class="isortRow"]/h3/text()')
result2 = list(filter(lambda x:x!='\r\n\t\t\t\t',result))
with open('./各大板块.txt','w',encoding='utf-8') as fp:
for i in result2:
fp.write(i)
先是从总的网页读取每一个板块的名称,因为读取出来的文字带有’\r\n\t\t\t’,于是利用filter过滤一下
这里有个问题,就是没有文字的仅是’\r\n\t\t\t’的内容被过滤了,但是文字+’\r\n\t\t\t’的怎么也处理不了,尝试了replace无果,于是手动改了,这也是为什么最后代码会有本地读取的原因(是我太菜了)
各个版块下具体分类名及链接地址抓取
for i in range(0,10):
urlresult = html.xpath('/html/body/div[2]/div[3]/div['+str(i+1)+']/a/@href')
nameresult = html.xpath('/html/body/div[2]/div[3]/div['+str(i+1)+']/a/text()')
因为网站分为两个div存放的位置,于是我具体读取的时候就分成两块读:0-10与0-8
网站信息抓取
接下来就根据每一个具体类别和链接,一遍一遍爬就好了
#以读取超市为例
url = "https://poi.mapbar.com/beijing/520"
response = requests.get(url)
txt = response.text
html = etree.HTML(txt)
result1 = html.xpath('//div[@class="sortC"]/dl/dd/a/text()')
result2 = html.xpath('//div[@class="sortC"]/dl/dd/a/@href')
信息存储
为了方便阅读,用了json格式存放,如果需要了解更多可以看下方心得体会
with open('./标准化.json', 'a', encoding='utf-8') as fp:
json.dump({'板块名称': list5[i + 10*m],
'具体分类': nameresult[j],
'链接地址': urlresult[j],
'名称': result1,
'相关链接': result2},
fp,ensure_ascii=False)
fp.write('\n')
三、心得体会
xpath定位
这里要讲一下xpath地址的抓取,因为没有什么基础,只要想要那个元素,但是不知道如何定位就是一个很大的问题
进入目标网站-右键-检查
你就可以看到里面的元素,然后根据选中的范围一层一层的找到你想要的元素
目标元素位置-右键-copy-xpath
这样做你可以获得它的绝对路径,但是一般来说我们用不着那么清晰
这个时候就要知道xpath好用的一点
先给出Xpath的表示式
表达式 | 描述 |
---|---|
/ | 从根节点选取 |
// | 从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置 |
. | 选取当前节点 |
… | 选取当前节点的父节点 |
@ | 选取属性。 |
我们可以看到超市里面第一个AEON的元素有<div class=“sortC”>
我们就可以根据这个进行定位
result1 = html.xpath('//div[@class="sortC"]/dl/dd/a/text()')
result2 = html.xpath('//div[@class="sortC"]/dl/dd/a/@href')
虽然最后的标签是a,但是a是一个对象,想要获取它的文字信息,一定要用a/text(),如果是获取他的超链接地址用a/@href
动态分页处理
在爬虫的时候,发现了一个问题,如下:
(公司企业-其他公司企业)
本来想着用xpath去定位,结果发现是一个JS动态生成的。在贾老师的帮助下知道了分页的机制:
- 如果访问的是无分页的网站,链接显示为:https://poi.mapbar.com/beijing/xxx/
- 如果访问的是动态分页的网站,链接显示为:
https://poi.mapbar.com/beijing/xxx_1/
根据分页不同更改数字即可 - 实际上访问https://poi.mapbar.com/beijing/xxx_1/ 实际就是在访问无分页情况下的https://poi.mapbar.com/beijing/xxx/
于是对于每一个链接的访问,都改成xxx_1的访问方式,在访问成功后,尝试性访问xxx_2,如果访问成功,则继续读取写入该类别;如果返回404就放弃,开始访问下一个类别
url = url[:len(url)-1]+"_1/"
t=1
while True:
url = url[:len(url)-2]+str(t)+"/"
response = requests.get(url)
if requests.get(url).status_code==404:
break
访问限制
因为爬取的数据量较大(最后合格的文档是60MB),本身访问请求次数较多,并且加上测试动态分页的情况,访问次数又会加倍,很容易被目标网站限(dang)制(chang)访(zhua)问(huo)
我先尝试了time.sleep,在测试0.3s,0.5s,1s都失败之后,我选择了分成两块爬取。本来想着中间sleep十分钟就可以了,结果继续被限制…
最后我将整个爬虫分成两个板块,不设置sleep,两次爬虫中间间隔3小时,get√
json储存格式
原本存放json的代码是这样的:
with open('./标准化.json', 'a', encoding='utf-8') as fp:
json.dump({'板块名称': list5[i + 10*m],
'具体分类': nameresult[j],
'链接地址': urlresult[j],
'名称': result1,
'相关链接': result2}, fp)
结果发现有乱码,并且难以阅读
于是变成了这样
with open('./标准化.json', 'a', encoding='utf-8') as fp:
json.dump({'板块名称': list5[i + 10*m],
'具体分类': nameresult[j],
'链接地址': urlresult[j],
'名称': result1,
'相关链接': result2},
fp,ensure_ascii=False)
fp.write('\n')
写入的每一行代表一个具体的设施信息,利用ensure_ascii=False来解决了问题