如之前的几篇文章《Python爬虫框架之Scrapy详解》、《Python爬虫框架Scrapy之爬取糗事百科大量段子数据)》,使用了Scrapy框架并且爬取了糗事百科的段子存入MongoDB中。
Scrapy框架很好,也提供了很多扩展点,可以自己编写中间件处理Scrapy的Request和Response。但是可定制化或者可掌控性来说,还是自己写的爬虫更加强一些。
如果写简单更加可控的爬虫,还是建议使用Python第三方库:requests和bs4。
requests和bs4爬空姐网图片
requests库
requests是Python非常流行的处理网络数据的第三方库。相对于Python内置框架urllib、urllib2来说,requests提供的操作更加简洁而且更加丰富。如requests示例:
#HTTP请求类型
#get类型
r = requests.get('https://github.com/timeline.json')
#post类型
r = requests.post("http://m.ctrip.com/post")
#put类型
r = requests.put("http://m.ctrip.com/put")
#delete类型
r = requests.delete("http://m.ctrip.com/delete")
#head类型
r = requests.head("http://m.ctrip.com/head")
#options类型
r = requests.options("http://m.ctrip.com/get")
#获取响应内容
print r.content #以字节的方式去显示,中文显示为字符
print r.text #以文本的方式去显示
#URL传递参数
payload = {'keyword': '日本', 'salecityid': '2'}
r = requests.get("http://m.ctrip.com/webapp/tourvisa/visa_list", params=payload)
print r.url #示例为http://m.ctrip.com/webapp/tourvisa/visa_list?salecityid=2&keyword=日本
#获取/修改网页编码
r = requests.get('https://github.com/timeline.json')
print r.encoding
r.encoding = 'utf-8'
#json处理
r = requests.get('https://github.com/timeline.json')
print r.json() #需要先import json
#定制请求头
url = 'http://m.ctrip.com'
headers = {'User-Agent' : 'Mozilla/5.0 (Linux; Android 4.2.1; en-us; Nexus 4 Build/JOP40D) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.166 Mobile Safari/535.19'}
r = requests.post(url, headers=headers)
print r.request.headers
#复杂post请求
url = 'http://m.ctrip.com'
payload = {'some': 'data'}
r = requests.post(url, data=json.dumps(payload)) #如果传递的payload是string而不是dict,需要先调用dumps方法格式化一下
#post多部分编码文件
url = 'http://m.ctrip.com'
files = {'file': open('report.xls', 'rb')}
r = requests.post(url, files=files)
#响应状态码
r = requests.get('http://m.ctrip.com')
print r.status_code
#响应头
r = requests.get('http://m.ctrip.com')
print r.headers
print r.headers['Content-Type']
print r.headers.get('content-type') #访问响应头部分内容的两种方式
#Cookies
url = 'http://example.com/some/cookie/setting/url'
r = requests.get(url)
r.cookies['example_cookie_name'] #读取cookies
url = 'http://m.ctrip.com/cookies'
cookies = dict(cookies_are='working')
r = requests.get(url, cookies=cookies) #发送cookies
#设置超时时间
r = requests.get('http://m.ctrip.com', timeout=0.001)
#设置访问代理
proxies = {
"http": "http://10.10.10.10:8888",
"https": "http://10.10.10.100:4444",
}
r = requests.get('http://m.ctrip.com', proxies=proxies)
通过requests,我们可以很方便的发送GET、POST、DELETE、PUT请求,获取相应数据等等。
bs4库
bs4是指BeautifulSoup 4.x版本。相对于BeautifulSoup 2.x和3.x,4.x提供了更加丰富和人性化的api。使用BeautifulSoup,我们可以很方便的定位到HTML中我们想要的元素,获取元素值等等。如:
soup.title
# <title>The Dormouse's story</title>
soup.title.name
# u'title'
soup.title.string
# u'The Dormouse's story'
soup.title.parent.name
# u'head'
soup.p
# <p class="title"><b>The Dormouse's story</b></p>
soup.p['class']
# u'title'
soup.a
# <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
soup.find_all('a')
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
# <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
soup.find(id="link3")
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>
查看空姐网网页结构
首先,我们查看一下空姐网的网页结构,找到每个人的相册页面。在kongjie.com里面随意翻翻,就能找到热门相册页面,如图:
首先,分析一下该页面结构,提取出每个人的相册页链接。如图:
class属性为ptw的div下,ul中的每一个li都是每个人的相册封面,通过提取li中的链接,就能进入每个人的相册。
开始爬取
提取相册链接
从上面这个页面提取每个人相册链接的css表达式为div.ptw li.d
。这样,我们就可以把这个表达式用在BeautifulSoup里面了。
如,
def parse_album_url(url):
"""
解析出相册url,然后进入相册爬取图片
"""
response = requests.get(url, headers=headers)
soup = BeautifulSoup(response.text, 'lxml')
people_list = soup.select('div.ptw li.d')
for people in people_list:
save_images_in_album(people.div.a['href'])
# 爬取下一页
next_page = soup.select_one('a.nxt')
if next_page:
parse_album_url(next_page['href'])
if __name__ == '__main__':
parse_album_url(start_url)
现在,获取到了每个人的相册链接,接下来就是编写save_images_in_album()方法,进入每个人的相册里面抓取图片了。再提一下,再这里,我们提取完一页中每个人的相册链接之后,解析了网页里的“下一页”的链接,这样就能自动翻页抓取了。“下一页”链接的网页结构如下:
通过css表达式a.nxt
就能提取到这个下一页链接。
进入相册提取图片
要编写爬虫,我们还是获取一下浏览器访问这个页面时的Request Headers,这样,就能绕过一些简单的反爬虫手段。
然后,进入到相册内部,查看一下网页结构,如图:
我们得知,id为photo_pic,class为c的div里面,第一个超链接里面的img标签就是大图。所以,我们在这里提取这个链接。我们使用soup.find('div', id='photo\_pic', class\_='c')
定位到id为photo_pic、class为c的div,然后通过image_div.a.img[‘src’]就能拿到这个图片的链接了。
拿到一张图片的链接之后,我们需要切换到下一张图片。可以看到,大图下面最后面有个向右的箭头,这个是下一张图的按钮,我们获取这个按钮的链接,获取连接对应的css表达式为div.pns.mlnv.vm.mtm.cl a.btn[title="下一张"]
,然后就可以重复上面两个步骤爬取相册里面所有的照片了。
图片去重
在这里,我们怎么知道一个相册里的图片都爬取完成了呢?
我们使用redis来存放爬取了的图片id,如果一张图片id已存在redis中了,那么不爬取了,这样,我们就能很方便的知道一个相册是否爬取完了(相册中所有图片id都存在redis中了,就表示该相册爬取完了)。
因此,我们写出save_images_in_album()方法如下:
def save_images_in_album(album_url):
"""
进入空姐网用户的相册,开始一张一张的保存相册中的图片。
"""
# 解析出uid和picid,用于存储图片的名字
uid_picid_match = uid_picid_pattern.search(album_url)
if not uid_picid_match:
return
else:
uid = uid_picid_match.group(1)
picid = uid_picid_match.group(2)
response = requests.get(album_url, headers=headers)
soup = BeautifulSoup(response.text, 'lxml')
image_div = soup.find('div', id='photo_pic', class_='c')
if image_div and not redis_con.hexists('kongjiewang', uid + ':' + picid):
image_src = domain_name + image_div.a.img['src']
save_img(image_src, uid, picid)
redis_con.hset('kongjie', uid + ':' + picid, '1')
next_image = soup.select_one('div.pns.mlnv.vm.mtm.cl a.btn[title="下一张"]')
if not next_image:
return
# 解析下一张图片的picid,防止重复爬取图片,不重复则抓取
next_image_url = next_image['href']
next_uid_picid_match = uid_picid_pattern.search(next_image_url)
if not next_uid_picid_match:
return
next_uid = next_uid_picid_match.group(1)
next_picid = next_uid_picid_match.group(2)
if not redis_con.hexists('kongjie', next_uid + ':' + next_picid):
save_images_in_album(next_image_url)
这里,我们从相册的url中,通过正则表达式:uid_picid_pattern = re.compile(r’.*?uid=(\d+).*?picid=(\d+).*?’)解析出用户id和每张图片的id。然后就可以用redis来去重了。
下载图片
在上面这个函数里,我们拿到了每张图片大图的链接,即image_src变量。然后我们就可以编写save_img()方法来保存图片了。如:
def save_img(image_url, uid, picid):
"""
保存图片到全局变量save_folder文件夹下,图片名字为“uid_picid.ext”。
其中,uid是用户id,picid是空姐网图片id,ext是图片的扩展名。
"""
try:
response = requests.get(image_url, stream=True)
# 获取文件扩展名
file_name_prefix, file_name_ext = os.path.splitext(image_url)
save_path = os.path.join(save_folder, uid + '_' + picid + file_name_ext)
with open(save_path, 'wb') as fw:
fw.write(response.content)
print uid + '_' + picid + file_name_ext, 'image saved!', image_url
except IOError as e:
print 'save error!', e, image_url
运行结果
最后,我们在命令行运行Python kongjiewang.py。看一下结果:
大功告成!
喜欢的可以关注我的WeiXin订阅号,随时随地学习: