python3爬虫
本人是个python爬虫小白,也没有任何编程经验,在一个偶然的机会浏览某论坛发现了大量的妹子图,于是想着能不能写个简单的爬虫将这些图片爬下来再看,于是在网上找各种资料发现python实现爬虫比较简单、入门也低,于是自己慢慢摸索写了个论坛妹子图片的多线程爬虫,实现将特定主题帖子中的妹子图爬取后保存在本地PC中。
先到www.python.org官网下载python安装包,建议新手学习使用python3吧,本人使用的是python-3.5.4-amd64安装包,安装完成后就可以使用python shell环境写代码了,但是这种环境不适合软件工程开发。
建议使用sublime text或者pycharm集成开发环境,sublime text适合轻量级软件工程开发,而pycharm适合大型软件工程开发,具体安装配置使用请自行百度下。
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页的图片资源。
完整GitHub源码链接
由于刚学习python,很多技巧还没有掌握,文章中的代码还有很多处是可以优化的,比如第一个函数和最后一个函数重复的代码比较多,希望高手能够指点下如何优化,大家互相学习嘛!