python3爬虫
序
本人是个python爬虫小白,也没有任何编程经验,在一个偶然的机会浏览某论坛发现了大量的妹子图,于是想着能不能写个简单的爬虫将这些图片爬下来再看,于是在网上找各种资料发现python实现爬虫比较简单、入门也低,于是自己慢慢摸索写了个论坛妹子图片的多线程爬虫,实现将特定主题帖子中的妹子图爬取后保存在本地PC中。
python3环境准备
先到www.python.org官网下载python安装包,建议新手学习使用python3吧,本人使用的是python-3.5.4-amd64安装包,安装完成后就可以使用python shell环境写代码了,但是这种环境不适合软件工程开发。
建议使用sublime text或者pycharm集成开发环境,sublime text适合轻量级软件工程开发,而pycharm适合大型软件工程开发,具体安装配置使用请自行百度下。
python3第三方库安装
python3本身也内置了urllib网页请求模块,但是我比较喜欢用requests这个库,因为简单易懂。
在windows cmd命令窗口中安装requests库:pip3 install requests
还有个就是网页解析库,网上多数人建议使用bs4库,但是我发现lxml库的效率比bs4快些,我个人比较倾向于使用lxml库。
在windows cmd命令窗口中安装lxml库:pip3 install lxml
最后用pip3 list确认下是否安装成功,如果cmd窗口中显示如下则表示安装成功了。
爬虫准备工作--分析网站结构
国内著名的MM图片论坛网站,url就不放出来了(容易被和谐,但我的GitHub源码中有),一般的论坛网站都是首页->论坛主题->帖子主题列表->帖子主题内容,这样的页面结构。可以根据这种页面结构来组织编写代码,代码实现逻辑如下图所示:
爬虫代码实现说明
-
get_html代码说明
def get_html(url):
try:
# 使用requests库中的get方法请求页面
r = s.get(url=url,headers=header,timeout=12)
if r.status_code == 404:
print(' %s 响应状态码非200 OK,正在重试中......' % url)
time.sleep(6)
r = s.get(url=url,headers=header,timeout=15)
global num
except Exception as err:
print(err,'\n请求 %s 失败,正在重试中......' % url)
time.sleep(3)
while True:
try:
r = s.get(url=url,headers=header,timeout=15)
if r.status_code == 404:
print(' %s 响应状态码非200 OK,正在重试中......' % url)
time.sleep(6)
r = s.get(url=url,headers=header,timeout=15)
except Exception as err:
print(err,'\n请求重试 %s 继续失败,继续重试中......' % url)
time.sleep(3)
else:
r.encoding = 'GB18030'
if r.text.find('404 Not Found') != -1:
print(' %s 重试失败!放弃访问!' % url,r)
return r.text
else:
print('请求重试 %s 成功!' % ('【'+str(num)+'】'+ url),r)
num += 1
return r.text
else:
r.encoding = 'GB18030'
if r.text.find('404 Not Found') != -1:
print(' %s 重试失败!放弃访问!' % url,r)
return r.text
else:
print(' %s 访问成功!' % ('【'+str(num)+'】'+ url),r)
num += 1
return r.text
其中最外层try中的代码块实现页面请求以及如果响应状态码非200ok时会重新请求一次;最外层的except中代码块捕捉到任何请求异常就会重新发起页面请求直至成功;最外层else中代码块作用为当try语句正常执行后就会执行else中的语句。
-
list_page代码说明
def list_page(url):
page = get_html(url)
# 使用lxml库的html模块解析页面
tree = html.fromstring(page)
# 先使用lxml的xpath方法获取所有帖子主题列表的标签,然后利用正则表达式过滤所需要的所有帖子主题列表url
pages_url = re.findall(r'htm_data/16/17\d+/\d{7}\.html',str(tree.xpath('//h3/a/@href')))
for i in range(len(pages_url)):
# 拼接真正的帖子主题列表url地址
pages_url[i] = 'http://cl.***.xyz/'+pages_url[i]
return pages_url
此函数的功能是获取每页中所有主题列表的url地址。
-
get_page_html代码说明
def get_page_html(url):
pages = list_page(url)
for page in pages:
page = get_html(page)
tree = html.fromstring(page)
# 使用lxml的xpath方法获取主题内容的标题
title_name = tree.xpath('//h4/text()')
if len(title_name) != 0:
# 替换不符合windows系统文件命名的特殊符号
file_name = title_name[0].replace(':','_')\
.replace('?','_')\
.replace('<','(')\
.replace('>',')')\
.replace('"',' ')\
.replace('*','_')\
.replace('\u301c','')\
.replace('\xa0','_')
else:
file_name = None
continue
# 使用lxml的xpath方法获取主题内容中图片的url地址
img_url_list = tree.xpath('//input/@src')
# 判断主题文件夹是否存在,如果没有则直接创建
if not os.path.exists('D:\\Download\\Pictrue\\'+file_name):
try:
os.makedirs('D:\\Download\\Pictrue\\' + file_name)
os.chdir('D:\\Download\\Pictrue\\' + file_name)
print('D:\Download\Pictrue\%s 主题目录创建成功!' % file_name)
except Exception as err:
print('主题目录创建失败!','\n'+err)
continue
else:
print('D:\Download\Pictrue\%s 主题目录已存在!' % file_name)
for img_url in img_url_list:
# 图片文件名和替换不符合系统文件命名规则的特殊符号
img_name = img_url.split('/')[-1].translate(str.maketrans('<>?=&','____.'))
# 判断图片是否下载过,如果没有就直接下载
if not os.path.isfile('D:\\Download\\Pictrue\\' + file_name+ '\\' + img_name):
# 调用多线程下载图片
thread = download_thread(file_name,img_url,img_name)
thread.start()
time.sleep(1.5)
else:
print(img_name+' >>> 该图片已经下载过了!')
print('该主题共有 %s 张图片!' % len(img_url_list))
time.sleep(5)
此函数的功能是获取每个帖子主题内容中的标题及图片的url地址并调用多线程下载图片
-
save_img代码说明
def save_img(file_name,img_url,img_name):
with open('D:\\Download\\Pictrue\\' + file_name + '\\' + img_name,'wb') as file:
print('正在使用线程 %s 下载:' % threading.current_thread().name + file_name +' >>> '+ img_name)
try:
file.write(s.get(img_url,headers=header,timeout=15).content)
print('线程 %s 下载完成!' % threading.current_thread().name)
except Exception as err:
print(err,'\n'+img_url+' >>> URL请求失败,正在重新下载......')
time.sleep(3)
# 下载失败会重试一次
try:
file.write(s.get(img_url,headers=header,timeout=18).content)
print('线程 %s 重新下载完成!' % threading.current_thread().name)
except Exception as err:
print(err,'\n'+img_url+' >>> URL请求继续失败,放弃下载......')
此函数功能是执行图片下载并保存在本地目录。
-
入口函数的代码说明
if __name__ == '__main__':
''' 1.爬取过于频繁会导致ip访问受限制,之后访问需要输入验证码
2.根据不同网络环境判断访问是否需要输入验证码
3.验证码保存在代码所在目录,并需要手动输入
'''
global r
r = s.get('http://cl.***.xyz/index.php',headers=header,timeout=12)
r.encoding = 'GB18030'
#print(r,'\n'+r.text)
if r.text.find('url=codeform.php') != -1:
with open('code.png', 'wb') as file:
print('正在下载验证码图片......\n请到 %s 目录找到code.png并手动输入!' % os.path.abspath('code.png'))
file.write(s.get('http://cl.***.xyz/require/codeimg.php',headers=header,timeout=15).content)
file.close()
code = input('请输入验证码:')
postdata['validate'] = code
r = s.post('http://cl.***.xyz/codeform.php',headers=header,data=postdata)
r.encoding = 'GB18030'
while r.text.find('输入有误') != -1:
print('验证码输入有误!')
with open('code.png', 'wb') as file:
file.write(s.get('http://cl.***.xyz/require/codeimg.php',headers=header,timeout=15).content)
file.close()
code = input('请重新输入验证码:')
postdata['validate'] = code
r = s.post('http://cl.***.xyz/codeform.php',headers=header,data=postdata)
r.encoding = 'GB18030'
for index in range(100):
index += 1
url = 'http://cl.***.xyz/thread0806.php?fid=16&search=&page='+str(index)
get_page_html(url)
time.sleep(3)
else:
for index in range(100):
index += 1
url = 'http://cl.***.xyz/thread0806.php?fid=16&search=&page='+str(index)
get_page_html(url)
time.sleep(3)
代码入口函数实现验证码登入(实际上如果爬取不是很频繁是不需要输入验证码的)及爬取前100页的图片资源。
由于刚学习python,很多技巧还没有掌握,文章中的代码还有很多处是可以优化的,比如第一个函数和最后一个函数重复的代码比较多,希望高手能够指点下如何优化,大家互相学习嘛!