上一篇学习了从小说网站上爬取内容的思路,这里总结一下:
- 使用requests获得网页的html信息
- 使用BeautifulSoup解析获得的html信息
- 从html中筛选出感兴趣的部分(如小说文本)并保存下来
这是大多数静态网站的爬虫思路。
除此之外,还存在一类所谓的动态网站。如图片网站就是一类动态网站,因为其中的图片是动态加载的,即需要图片显示时(如下拉)它才会显示。
静态网站的内容是直接放在网页的html文件中的,而动态网站中的内容则不是这样的。比如“百度图片”要动态显示一张图片,它会首先向另一个地址发出请求,获得该图片的链接地址,通过此地址加载图片并显示出来。也就是说,图片的地址信息并没有直接放在“百度图片”的html文件中,而是以一定的数据格式(如json)保存在一个中间网址上,该中间网址用来回应前端网页的请求。此外,图片的信息并不是存放于一个中间网址上,而是多个,所以一个中间网址上除了包含有限张(如9张)图片的链接,同时也包含指向下一页的中间网址.
所以,要实现图片的下载,首先要找到网页的请求网址,然后解析请求网址上的网页信息以获得图片的链接,最后通过该链接来下载图片。
本文中爬虫的思路:
- 使用fiddler软件来获得请求网站的地址
- 解析请求网址上的json信息,获得图片id和链接地址
- 利用图片的链接地址下载图片
# picture_downloader.py
'''图片下载器'''
import requests, json, time, sys
from contextlib import closing
class get_photos:
def __init__(self):
self.photos_id = [] #存放图片id
self.download_server = 'https://unsplash.com/photos/xxx/download?force=true' #图片的地址格式
self.target = 'http://unsplash.com/napi/feeds/home' #获得的请求网址
self.headers = {'authorization':'Client-ID **********'}
#以上信息均由抓包软件fiddler获得
"""
函数说明:获取图片ID
"""
def get_ids(self):
req = requests.get(url=self.target, headers=self.headers, verify=False)
html = json.loads(req.text)
next_page = html['next_page'] #获得下一页的地址(下一个请求网址)
for each in html['photos']:
self.photos_id.append(each['id']) #存放本页的图片的id
time.sleep(1) #休息一秒在爬
for i in range(1): #设置页数
req = requests.get(url=next_page, headers=self.headers, verify=False) #爬下一页的
html = json.loads(req.text)
next_page = html['next_page']
for each in html['photos']:
self.photos_id.append(each['id'])
time.sleep(1)
"""
函数说明:图片下载
"""
def download(self, photo_id, filename):
headers = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.62 Safari/537.36'}#由抓包软件获得
target = self.download_server.replace('xxx', photo_id) #图片的地址
with closing(requests.get(url=target, stream=True, verify = False, headers = self.headers)) as r:
with open('%d.jpg' % filename, 'ab+') as f:
for chunk in r.iter_content(chunk_size = 1024):
if chunk:
f.write(chunk)
f.flush()
if __name__ == '__main__':
gp = get_photos()
print('获取图片连接中:')
gp.get_ids()
print('共有 %d 张图片:' % len(gp.photos_id))
for i in range(len(gp.photos_id)):
print(' 正在下载第%d张图片' % (i+1))
gp.download(gp.photos_id[i], (i+1))
print('下载完成!')
涉及到的知识点:
关于图片的下载方式:
#这是下载流文件的标准格式,具体见requests的帮助文档.
with closing(requests.get(url=target, stream=True, verify = False, headers = self.headers)) as r:
with open('%d.jpg' % filename, 'ab+') as f:
for chunk in r.iter_content(chunk_size = 1024):
if chunk:
f.write(chunk)
f.flush()
其中,
[stream=True]
注明要获取来自服务器的原始套接字响应,即以字节流形式下载原始数据.
[verify = False, headers = self.headers]
伪装自己,用于躲避请求网站的服务器的反爬虫侦察。
[req]
服务器返回的req是一个response对象。
所谓的response对象,其实就是服务器发回来的关于你请求访问的网址的所有属性信息的集合,你可以从中获得想要的东西.[with closing(req) as r]
创建上下文管理器,在执行过程离开with语句体时自动执行req.close()。
with closing(<module>.open(<arguments>)) as f:
<block>
相当于:
f=<module>.open(<arguments>)
try:
<block>
finally:
f.close()
f.flush()的作用
一般的文件流操作都包含缓冲机制,write方法并不直接将数据写入文件,而是先写入内存中特定的缓冲区。
flush方法是用来刷新缓冲区的,即将缓冲区中的数据立刻写入文件,同时清空缓冲区。
正常情况下缓冲区满时,操作系统会自动将缓冲数据写入到文件中。
至于close方法,原理是内部先调用flush方法来刷新缓冲区,再执行关闭操作,这样即使缓冲区数据未满也能保证数据的完整性。
如果进程意外退出或正常退出时而未执行文件的close方法,缓冲区中的内容将会丢失。
反爬虫的初步了解
利用抓包工具得到Requests Headers信息如下:
其中用红线圈起来的三个参数是常用的用来检查爬虫的手段.
- User-Agent:显示访问的浏览器的信息.如果不进行设置,服务器收到的会是一个带pyhton特征的字,可能拒绝访问.本例中好像没有用到.
- Referer:表示该请求是从哪里发出的.比如动态加载图片时,由https://unsplash.com/向服务器发送请求,这个值就是https://unsplash.com/;如果服务器对这个值进行设置,其它人就会无法访问.
- Authorization:这个参数是基于AAA模型中的身份验证信息允许访问一种资源的行为。在我们用浏览器访问的时候,服务器会为访问者分配这个用户ID。如果后台设计者,验证这个参数,对于没有用户ID的请求一律禁止访问,这样就又起到了反爬虫的作用。
# test01.py
from urllib.request import urlopen
from bs4 import BeautifulSoup
# 1.网页地址
url = "http://pythonscraping.com/pages/page1.html"
# 2.发送HTTP请求
html = urlopen(url)
# 3.对返回的网页文件进行解析
bsObj = BeautifulSoup(html.read(),"lxml")
# 4.打印出所需要的部分
print(bsObj.h1)