本文为 one-ccs 原创文章,引用必须注明出处!
最初是看到了Python爬虫入门教程:超级简单的Python爬虫教程,就有点感兴趣。正好这几天有时间,就一头钻了进去。经过几天的研究,也算有了一些了解。
以爬取壁纸吧的图片为例,输入首页地址,自动爬取所有页面的图片
注:以下使用的所有库,在上一节“准备”中都给出了对应的链接,且你在里面能学到的必然远远超过接下来所学的,以下仅仅做为一个示例,抛砖引玉,让我们开始吧!
1. 使用 requests 获取网页源代码
本来是用的python内置的 urlllib 但是这个库用起来特麻烦(特别是处理字符串编码的时候),最后换成了第三方的 requests
''' 使用 requests '''
import requests
from requests.exceptions import ReadTimeout, HTTPError, RequestException
url = '...'
# 尝试执行 try 缩进的代码,捕获错误后执行对应语句,否则报错并结束程序的运行
try:
# 请求 url 获得一个
response = requests.get(url, timeout=2.5)
except ReadTimeout as e:
# 捕获 ReadTimeout 错误,并临时命名为 e
print('Error: 连接超时 ->', url)
except HTTPError as e:
# 捕获 HTTPError 错误,并临时命名为 e
print('Error: 无效的 url 链接 ->', url)
except RequestException as e:
# 捕获 RequestException 错误,并临时命名为 e
print('Error: 请求异常 ->', url)
except Exception as e:
# 极端做法,捕获 所有 错误,并临时命名为 e
print('Error:', e, '->', url)
else:
# 当 try 语句块正确执行就会执行 else 语句块
# 提取网页源代码
text = response.text
print(text)
## 如果输出有乱码,试试
# print(text.encode('utf-8'))
## gbk, gb2312 是 gb18030 的子集,使用后者比较保险
# print(text.encode('gb18030'))
## apparent_encoding -> requests 用 chardet 对字节流猜测获得的编码
# print(text.encode(response.apparent_encoding))
''' 使用 urllib '''
from urllib import request
url = 'https://tieba.baidu.com/p/6197666814?pn=1'
# 请求 url 获得一个
response = request.urlopen(url)
# 获取网页的源代码的字节流
b_text = response.read()
# 解码获取对应的网页源码
text = b_text.decode('utf-8')
print(text)
2. 选择合适的正则表达式
随便打开一条帖子,复制图片地址。然后右击 -> 查看网页源代码 -> Ctrl+F -> 搜索复制的地址
搜索成功,并且发现图片路径是使用的绝对路径,这下好办了!
观察链接规律,使用正则 (?<=src=").+?\.jpg
匹配多达83个,有许多都是不需要的链接。
对表达式进行优化 (?<=src=").+?\.jpg(?=" size="\d{4,7})
优化后匹配39个,正好。
3. 使用 re 筛选
''' 使用正则表达式筛选可用链接 '''
import requests
import re
url = '...'
response = requests.get(url)
text = response.text
# 正则表达式
regex = r'(?<=src=").+?\.jpg(?=" size="\d{4,7})'
# 编译正则表达式
pattern = re.compile(regex)
# 获得全部匹配的字符串
# match[0]
match = re.findall(pattern, text)
for s in match:
print(s)
4. 使用 urllib.request 下载
既然获取了下载链接,那么下一步自然就是把图片下载下来了
''' 下载 '''
# 保存地址,这里使用的相对路径
path = './download/01.jpg'
# 下载,这里忽略了回调函数
request.urlretrieve(match[0], path)
5. 获取第 n 页链接
现在下载首页的图片没有问题了,那怎么自动下载第 n 页呢?
又是推理时间:
第一页 .../p/6197666814?pn=1
第二页 .../p/6197666814?pn=2
观察可知只有最后一个数字发生了变化,那猜测,最后一个数字是不是代表页数呢?
可推出第6页会不会是 .../p/6197666814?pn=6
呢?
经验证后,果然是这样
接下来就是一页一页的下载所有页面了,很容易就想到了循环使用字符串的 format 方法
''' 获取第 n 页链接 '''
url = '...'
# 字符串模板
url_model = url[:-4] + '{}'
page = 1 # 起始页
end_page = 100 # 结束页
while page <= end_page:
print(url_model.format('pn=' + str(page)))
page += 1
6. 结束
恭喜,你已经成功爬取了一堆预览图
ヽ(#`Д´)ノ 解决办法是,继续推理
预览图地址
/forum/w%3D580/sign=48d1e6...8/c61ca8d3fd1f413455c8b5922b1f95cad0c85e65.jpg
/forum/w%3D580/sign=2266b3...a/ec5694eef01f3a29898b65229725bc315d607c99.jpg
原图地址
/forum/pic/item/c61ca8d3fd1f413455c8b5922b1f95cad0c85e65.jpg
/forum/pic/item/ec5694eef01f3a29898b65229725bc315d607c99.jpg
观察发现文件名是不变的,只有路径变了
ok,那好办给所有下载链接变个装
''' 使用 url2fn 提取文件名 '''
def url2fn(url):
""" 返回 url 中的文件名 """
if not isinstance(url, str):
raise ValueError(
'The argument "url" should be a "str", but it is a "{}".'.format(type(url)))
flag = url[::-1].find('/')
if flag == -1:
return None
file = url[-flag:]
return file
''' 获取所有文件名 '''
filenames = map(url2fn, match)
for filename in filenames:
print(filename)
''' 组合链接 '''
# 获取所有文件名
filenames = map(url2fn, match)
# 转换类型
filenames = list(filenames)
# 拼接所有链接
real_urls = map(lambda x : '.../forum/pic/item/' + x, filenames)
path = './download/'
for url, name in zip(real_urls, filenames):
print('Download:', url)
request.urlretrieve(url, path + name)
7. 真的结束了
整理所有代码
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import requests
import re
from requests.exceptions import ReadTimeout, HTTPError, RequestException
from urllib import request
def check_url(url):
"""对首页链接的特殊情况进行特殊处理
若首页链接没有带页码,则自动加上页码方便后续处理
"""
if url.find('?') == -1:
url += '?pn=1'
return url
def get_text(url):
"""返回网页源码"""
text = None
try:
# 请求 url 获得一个
response = requests.get(url, timeout=2.5)
except ReadTimeout as e:
# 捕获 ReadTimeout 错误,并临时命名为 e
print('Error: 连接超时 ->', url)
except HTTPError as e:
# 捕获 HTTPError 错误,并临时命名为 e
print('Error: 无效的 url 链接 ->', url)
except RequestException as e:
# 捕获 RequestException 错误,并临时命名为 e
print('Error: 请求异常 ->', url)
except Exception as e:
# 极端做法,捕获 所有 错误,并临时命名为 e
print('Error:', e, '->', url)
else:
# 当 try 语句块正确执行就会执行 else 语句块
# 提取网页源代码
text = response.text
return text
def get_urls(text, regex):
"""筛选链接
Args:
text: 待匹配字符串
regex: 正则表达式
"""
# 编译正则表达式
pattern = re.compile(regex)
# 获得全部匹配的字符串
# match[0]
match = re.findall(pattern, text)
return match
def url2fn(url):
"""返回 url 中的文件名"""
if not isinstance(url, str):
raise ValueError(
'The argument "url" should be a "str", but it is a "{}".'.format(type(url)))
flag = url[::-1].find('/')
if flag == -1:
return None
file = url[-flag:]
return file
def main(url):
"""主函数"""
# 字符串模板
url_model = url[:-4] + '{}'
# 正则表达式
regex = r'(?<=src=").+?\.jpg(?=" size="\d{4,7})'
# 保存地址,这里使用的相对路径
path = './download/'
page = 1 # 起始页
end_page = 100 # 结束页
while page <= end_page:
url = url_model.format('pn=' + str(page))
print(url)
text = get_text(url)
urls = get_urls(text, regex)
# 获取所有文件名
filenames = map(url2fn, urls)
# 转换类型
filenames = list(filenames)
# 拼接所有链接
real_urls = map(lambda x : '.../forum/pic/item/' + x, filenames)
for url, name in zip(real_urls, filenames):
print('Download:', url)
request.urlretrieve(url, path + name)
page += 1
return None
if __name__ == "__main__":
while True:
url = input('请输入链接: ')
url = check_url(url)
main(url)
result = input('\n是否继续执行? [Y/N]: ')
if result == 'N' or result == 'n':
exit(0)
你学到了:
还需要注意:Python爬虫基本上都是一次性的,但万物不离根,都是这么个思路,只需要根据实际情况制定不同的策略。