引言html
本节开始学习Python爬虫,为何直接开始学爬虫?由于实用性高啊, 练手项目多(那么多网站能够爬),后面还能够作数据分析等等,还有最终 要的,能够扒不少小姐姐到硬盘里啊。什么是爬虫?个人理解: 模拟用户使用浏览器请求网站,而后经过一些手段获取到想要的信息; 好比在一个小网站上看到了不少漂亮的小姐姐图片,你想把他们都存 到你的硬盘里,天天没事就拿出来把玩一下。python
若是你不会爬虫,你只能重复地:程序员
右键图片->图片另存为->选择文件夹->改下图片名->保存正则表达式
呆得一匹,若是你学会了爬虫,你只须要花几分钟编写爬虫脚本: 而后执行下Py脚本,你能够去玩把跳一跳,回来就能够看到小姐姐 们都乖乖地趟在你的硬盘里了:chrome
对的,就是那么酷炫,程序员就应该作些酷酷的事! 关于Python爬虫,主要由两个部分组成:json
依次说下这两个部分吧,先是拿到正确网页源码:api
并非全部的源码都是躺在那里等你直接扒的,不少网站都会有 反爬虫策略,好比:须要点击阅读全文才能加载全部内容;数据 使用Js动态生成(图片站点尤多);这仍是不须要登陆的状况,有些 站点须要登陆后才能访问,模拟登陆验证码能够卡死一堆人: 数字图片模糊验证,计算数字运算验证,滑动条验证,滑动图片验证, 滑动坏块完成拼图验证,geetest那种人机验证...浏览器
接着是经过技术手段拿到目标信息:bash
这个倒没什么,最多也就是Js动态生成网页,这个能够经过后面学 的selenium来规避,反正浏览器显示的是什么样,拿到的网页源码 就是怎么样。(还有一种恶心的:把文本数据写到图片上,让你抓到 图片也迫不得已,可使用ORC的一些库进行文字识别,好比 tesseract-orc可是结果都是有些误差的,还要本身另外处理...) 有正确的网页源码了,基本上都是找到咱们想要的数据的,学习 一些HTML,XML的解析库就能够了,好比:BeautifulSoup,lxml等, 随便掌握一个就行了,笔者学习的是BeautifulSoup(甜汤)。服务器
说下爬虫学习路线吧:
1.先把基础核心库urllib学习一波; 2.接着学习一波BeautifulSoup来解析网页数据; 3.学习正则表达式 4.使用Scrapy框架爬取数据 5.使用Selenium模拟浏览器 6.多线程抓取 等等...
关于爬虫的基础姿式就了解到这里,开始本节内容~
用于操做URL的模块(库,py3把urllib和urllib2合并到了一块儿) 爬网页必须掌握的最基础的东东。
import urllib.request
import urllib.parse
import json
# 爬取网页信息
html_url = "http://www.baidu.com"
html_resp = urllib.request.urlopen(html_url)
# 读取所有,读取一行可用readline(),多行返回列表的可用readlines()
html = html_resp.read()
html = html.decode('utf-8') # 解码
print(html)
# 得到其余信息:
html_resp.info() # 得到头相关的信息,HTTPMessage对象
html_resp.getcode() # 得到状态码
html_resp.geturl() # 获取爬取的url
# url中包含汉字是不符合URL标准的,须要进行编码
urllib.request.quote('http://www.baidu.com')
# 编码后:http%3A//www.baidu.com
urllib.request.unquote('http%3A//www.baidu.com')
# 解码后:http://www.baidu.com
复制代码
# 下载图片
pic_url = "http://static.zybuluo.com/coder-pig/agr9d5uow8r5ug8iafnl6dlz/1.jpg"
pic_resp = urllib.request.urlopen(pic_url)
pic = pic_resp.read()
with open("LeiMu.jpg", "wb") as f:
f.write(pic)
# 也能够直接调用urlretrieve下载,好比下载音频
music_url = "http://7xl4pr.com2.z0.glb.qiniucdn.com/" \
"%E4%B8%83%E7%94%B0%E7%9C%9F%E4%B8%93%E5%8C%BA%2F%E4%" \
"B8%AD%E6%96%87%E8%AF%BE%2F%E6%83%B3%E8%" \
"B1%A1%E7%82%B9%E5%8D%A1%2F%2B6.mp3"
urllib.request.urlretrieve(music_url, "儿歌.mp3")
复制代码
PS:下面用到的**json模块
**:用于将Python原始类型与json类型相互转换,使用 若是是读取文件能够用:dump()和load()方法,字符串的话用下述两个:
dumps()编码
[Python -> Json] dict => object list, tuple => array str => string True => true int, float, int- & float-derived Enums => number False => false None => null
loads()解码
[Json -> Python] object => dict array => list string => str number (int) => int number(real) => float true =>True false => False null => None
# 模拟Get
get_url = "http://gank.io/api/data/" + urllib.request.quote("福利") + "/1/1"
get_resp = urllib.request.urlopen(get_url)
get_result = json.loads(get_resp.read().decode('utf-8'))
# 这里后面的参数用于格式化Json输出格式
get_result_format = json.dumps(get_result, indent=2,
sort_keys=True, ensure_ascii=False)
print(get_result_format)
# 模拟Post
post_url = "http://xxx.xxx.login"
phone = "13555555555"
password = "111111"
values = {
'phone': phone,
'password': password
}
data = urllib.parse.urlencode(values).encode(encoding='utf-8')
req = urllib.request.Request(post_url, data)
resp = urllib.request.urlopen(req)
result = json.loads(resp.read()) # Byte结果转Json
print(json.dumps(result, sort_keys=True,
indent=2, ensure_ascii=False)) # 格式化输出Json
复制代码
有些网站为了不别人使用爬虫恶意采起信息会进行一些反爬虫的操做, 好比经过请求头里的User-Agent,检查访问来源是否为正常的访问途径, 咱们能够修改请求头来进行模拟正常的访问。Request中有个headers参数, 有两种方法进行设置: 1.把请求头都塞到字典里,实例化Request对象的时候传入 2.经过Request对象的add_header()方法一个个添加
# 修改头信息
novel_url = "http://www.biqukan.com/1_1496/"
headers = {'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) '
'AppleWebKit/537.36 (KHTML, like Gecko)'
' Chrome/63.0.3239.84 Safari/537.36',
'Referer': 'http://www.baidu.com',
'Connection': 'keep-alive'}
novel_req = urllib.request.Request(novel_url, headers=headers)
novel_resp = urllib.request.urlopen(novel_req)
print(novel_resp.read().decode('gbk'))
复制代码
# urlopen函数时添加timeout参数,单位是秒
urllib.request.urlopen(novel_req, timeout=20)
复制代码
通常服务器会对请求的IP进行记录,若是单位时间里访问的次数达到一个阀值, 会认为该IP地址是爬虫,会弹出验证码验证或者直接对IP进行封禁。一个最简单 的方法就是延迟每次提交的时间,直接用**time模块
的sleep(秒)** 函数休眠下。
对应限制ip访问速度的状况咱们可使用延迟提交数据的作法, 可是有些是限制访问次数的,同一个ip只能在一段时间里访问 多少次这样,并且休眠这种方法效率也是挺低的。更好的方案是 使用代理,经过代理ip轮换去访问目标网址。 用法示例以下:
# 使用ip代理
ip_query_url = "http://www.whatismyip.com.tw"
# 1.建立代理处理器,ProxyHandler参数是一个字典{类型:代理ip:端口}
proxy_support = urllib.request.ProxyHandler({'http': '221.214.110.130:8080'})
# 2.定制,建立一个opener
opener = urllib.request.build_opener(proxy_support)
# 3.安装opener
urllib.request.install_opener(opener)
headers = {
'User-Agent': 'User-Agent:Mozilla/5.0 (X11; Linux x86_64)'
' AppleWebKit/537.36 (KHTML, like Gecko)'
' Chrome/63.0.3239.84 Safari/537.36',
'Host': 'www.whatismyip.com.tw'
}
MAX_NUM = 10 # 有时网络堵塞,会报URLError错误,因此加一个循环
request = urllib.request.Request(ip_query_url, headers=headers)
for i in range(MAX_NUM):
try:
response = urllib.request.urlopen(request, timeout=20)
html = response.read()
print(html.decode('utf-8'))
break
except:
if i < MAX_NUM - 1:
continue
else:
print("urllib.error.URLError: <urlopen error timed out>")
复制代码
输出结果:
如图代理成功,这里的网站是用于查询请求ip的,另外,咱们通常会弄一个 代理ip的列表,而后每次随机的从里面取出一个来使用。
Cookie定义:指某些网站为了辨别用户身份、进行 session 跟踪而储存在用户本地终端上的数据(一般通过加密) 好比有些页面你在登陆前是没法访问的,你登陆成功会给你分配 Cookie,而后你带着Cookie去请求页面才能正常访问。 使用http.cookiejar这个模块能够帮咱们获取Cookie,实现模拟登陆。 该模块的主要对象(父类->子类): CookieJar –> FileCookieJar –>MozillaCookieJar与LWPCookieJar
由于暂时没找到合适例子,就只记下关键代码,后续有再改下例子:
# ============ 得到Cookie ============
# 1.实例化CookieJar对象
cookie = cookiejar.CookieJar()
# 2.建立Cookie处理器
handler = urllib.request.HTTPCookieProcessor(cookie)
# 3.经过CookieHandler建立opener
opener = urllib.request.build_opener(handler)
# 4.打开网页
resp = opener.open("http://www.zhbit.com")
for i in cookie:
print("Name = %s" % i.name)
print("Name = %s" % i.value)
# ============ 保存Cookie到文件 ============
# 1.用于保存cookie的文件
cookie_file = "cookie.txt"
# 2.建立MozillaCookieJar对象保存Cookie
cookie = cookiejar.MozillaCookieJar(cookie_file)
# 3.建立Cookie处理器
handler = urllib.request.HTTPCookieProcessor(cookie)
# 4.经过CookieHandler建立opener
opener = urllib.request.build_opener(handler)
# 5.打开网页
resp = opener.open("http://www.baidu.com")
# 6.保存Cookie到文件中,参数依次是:
# ignore_discard:即便cookies将被丢弃也将它保存下来
# ignore_expires:若是在该文件中cookies已存在,覆盖原文件写入
cookie.save(ignore_discard=True, ignore_expires=True)
# ============ 读取Cookie文件 ============
cookie_file = "cookie.txt"
# 1.建立MozillaCookieJar对象保存Cookie
cookie = cookiejar.MozillaCookieJar(cookie_file)
# 2.从文件中读取cookie内容
cookie.load(cookie_file, ignore_expires=True, ignore_discard=True)
handler = urllib.request.HTTPCookieProcessor(cookie)
opener = urllib.request.build_opener(handler)
resp = opener.open("http://www.baidu.com")
print(resp.read().decode('utf-8'))
复制代码
对于url有时咱们须要进行编解码,好比带有中文的url,能够调用:
urllib.request.quote(xxx) # 编码
urllib.request.unquote(xxx) # 解码
复制代码
一个能够从 HTML
或 XML
文件中提取数据的 Python库,它可以经过 你喜欢的转换器实现惯用的文档导航、查找、修改文档的方式。 Beautiful Soup 会帮你节省数小时甚至数天的工做时间。
简单点说就是 爬取HTML和XML的利器
PyCharm直接安装: File -> Default Settings -> Project Interpreter 选择Python 3的版本 -> 点+号 -> 搜索beautifulsoup4 安装便可
pip方法安装的本身行百度,或者看上一节的内容~
简单点说这一步就是把html丢到BeautifulSoup对象里,能够是请求后的网站, 也能够是本地的HTML文件,第二个参数是指定解析器,html.parser是内置的 html解析器,你还能够用lxml.parser,不过要另外导库。
网站:soup = BeautifulSoup(resp.read(), 'html.parser') 本地:soup = BeautifulSoup(open('index.html'),'html.parser')
另外还能够调用**soup.prettify()
**格式化输出HTML
一.Tag(标签)
查找的是:在全部内容中第一个符合要求的标签,最经常使用的两个东东: tag.name
:得到tag的名字,好比body tag.attrs
:获取标签内全部属性,返回一个字典,能够根据键取值,也能够 直接调用get('xxx')拿到属性。
还有个玩法是:能够soup.body.div.div.a 这样玩,同过加标签名的形式 轻松获取标签的内容,不过查找的只是第一个!基本没什么卵用...
二.NavigableString(内部文字)
获取标签内部的文字,直接调用**.string**
三.BeautifulSoup(文档的所有内容)
当作一个Tag对象就好,只是能够分别获取它的类型,名称,一级属性而已
四.Comment(特殊的NavigableString)
这种对象调用**.string来输出内容会把注释符号去掉**,直接把注释里的内容 打出来,须要加一波判断:
if type(soup.a.string)==bs4.element.Comment:
print soup.a.string
复制代码
PS:就是找到了目标节点附近的节点,而后顺藤摸瓜找到目标节点。
子节点与子孙节点: contents:把标签下的全部子标签存入到列表,返回列表 children:和contents同样,可是返回的不是一个列表而是 一个迭代器,只能经过循环的方式获取信息,类型是:list_iterator 前二者仅包含tag的直接子节点,若是是想扒出子孙节点,可使用descendants 会把全部节点都剥离出来,生成一个生成器对象<class 'generator'>
父节点与祖先节点 parent:返回父节点Tag parents:返回祖先节点,返回一个生成器对象
兄弟结点: 处于同一层级的结点,next_sibling下一个,previous_sibling 上一个,结点不存在返回None 全部兄弟节点:next_siblings,previous_sibling,返回一个生成器对象
先后结点:next_element,previous_element 全部先后结点:next_elements,previous_elements 返回一个生成器对象
最经常使用的方法:
**find_all
(self, name=None, attrs={}, recursive=True, text=None, limit=None, kwargs):
name参数:经过html标签名直接搜索,会自动忽略字符串对象, 参数能够是:字符串,正则表达式,列表,True或者自定义方法
keyword参数:经过html标签的id,href(a标签)和title,class要写成class_, 能够同时过滤多个,对于不能用的tags属性,能够直接用一个attrs字典包着, 好比:find_all(attrs={'data-foo': 'value'}
text:搜索文档中的字符串内容
limit:限制返回的结果数量
recursive:是否递归检索全部子孙节点
其余方法:
find
(self, name=None, attrs={}, recursive=True, text=None, kwargs): 和find_all做用同样,只是返回的不是列表,而是直接返回结果。find_parents()
和find_parent()
: find_all() 和 find() 只搜索当前节点的全部子节点,孙子节点等. find_parents() 和 find_parent()用来搜索当前节点的父辈节点, 搜索方法与普通tag的搜索方法相同,搜索文档搜索文档包含的内容。find_next_sibling()
和find_next_siblings()
: 这2个方法经过 .next_siblings 属性对当 tag 的全部后面解析的兄弟 tag 节点进行迭代, find_next_siblings() 方法返回全部符合条件的后面 的兄弟节点,find_next_sibling() 只返回符合条件的后面的第一个tag节点。find_previous_siblings()
和find_previous_sibling()
: 这2个方法经过 .previous_siblings 属性对当前 tag 的前面解析的兄弟 tag 节点进行迭代, find_previous_siblings()方法返回全部符合条件的前面的 兄弟节点, find_previous_sibling() 方法返回第一个符合条件的前面的兄弟节点。find_all_next()
和find_next()
: 这2个方法经过 .next_elements 属性对当前 tag 的以后的 tag 和字符串进行 迭代, find_all_next() 方法返回全部符合条件的节点, find_next() 方法返回 第一个符合条件的节点。find_all_previous()
和find_previous()
这2个方法经过 .previous_elements 属性对当前节点前面的 tag 和字符串 进行迭代, find_all_previous() 方法返回全部符合条件的节点, find_previous()方法返回第一个符合条件的节点上一节学习了基础知识,这节又学了简单的爬虫姿式:urllib和Beautiful Soup库, 确定是要来个实战练练手的,选了个最简单的并且有点卵用的小东西玩玩。 相信各位大佬平时都有看网络小说的习惯吧,应该不多有老司机去会起点 付费看,通常都有些盗版小说网站,好比:笔趣看:www.biqukan.com/ 手机在线看,广告是可怕的,想一想你在挤满人的地跌上,忽然蹦出一对 柰子,你的第一反应确定是关掉,然而仍是naive,点X直接弹一个新的 网页什么壮阳延时...尴尬得一匹。学了Python爬虫的东西了,写个小爬虫 爬爬小说顺道练练手岂不美滋滋。不说那么多,开搞: 我最近在看的小说:唐家三少的《斗罗大陆3-龙王传说》 www.biqukan.com/1_1496/
图中圈住的部分就是小说的章节,F12打开chrome的开发者工具,切到Network选项卡 (或者直接看Elements也行),选Response,刷新一波能够看到这样的HTML结构:
固然咱们要找的内容不在这里,继续往下翻:
listmain这个全局搜了下,是惟一的(好吧,找这个真的太没难度了...) 直接find_all(attrs={'class': 'listmain'}) 就能够拿到这段东西了 find_all返回一个bs4.element.ResultSet 对象,for循环遍历一波 这个对象,(迭代对象的类型是**:bs4.element.Tag**)打印一波能够看到:
咱们要留下的只是<a>xxx</a>
这种东西,能够在循环的时候顺带把 无关的筛选掉:
能够打印下这个a_list,剩下的全是<a>xxx</a>
由于最新章节列表那里默认有12个,咱们应该从楔子那里开始, 因此把把a_list列表分下片:result_list = a_list[12:] 过滤掉前面的最新章节部分~
章节部分的数据就处理完毕了,有章节内容的url,以及章节的名称, 接着咱们来看看章节页面的内容结构,随便打开一个: 好比:www.biqukan.com/1_1496/4503…
就不说了,class="showtxt"又是惟一的,直接: showtxt = chapter_soup.find_all(attrs={'class': 'showtxt'}) 把showtxt循环打印一波,里面的东西就是咱们想要的东东了~
url有了,章节名有了,内容有了,是时候写入到文件里了,这个 过于简单就不用多说了,strip=True表明删除字符先后的全部空格:
到此咱们爬区小说的小爬虫就写完了,不过有个小问题是,批量 快速访问的时候,会报503异常,由于服务器通常会对限制ip在 一段时间里访问的频次,上面也讲了要么休眠,要么搞ip代理, 确定是搞ip代理嗨一些,接着咱们来写一个爬虫来抓取西刺 代理的ip,并校验是否可用,而后存到本地,咱们的代理ip池, 哈哈~
附上小说抓取这部分的代码:
from bs4 import BeautifulSoup
import urllib.request
from urllib import error
novel_url = "http://www.biqukan.com/1_1496/" # 小说页面地址
base_url = "http://www.biqukan.com" # 根地址,用于拼接
save_dir = "Novel/" # 下载小说的存放路径
# 保存小说到本地
def save_chapter(txt, path):
try:
with open(path, "a+") as f:
f.write(txt.get_text(strip=True))
except (error.HTTPError, OSError) as reason:
print(str(reason))
else:
print("下载完成:" + path)
# 得到全部章节的url
def get_chapter_url():
chapter_req = urllib.request.Request(novel_url)
chapter_resp = urllib.request.urlopen(chapter_req, timeout=20)
chapter_content = chapter_resp.read()
chapter_soup = BeautifulSoup(chapter_content, 'html.parser')
# 取出章节部分
listmain = chapter_soup.find_all(attrs={'class': 'listmain'})
a_list = [] # 存放小说全部的a标签
# 过滤掉不是a标签的数据
for i in listmain:
if 'a' not in str(i):
continue
for d in i.findAll('a'):
a_list.append(d)
# 过滤掉前面"最新章节列表"部分
result_list = a_list[12:]
return result_list
# 获取章节内容并下载
def get_chapter_content(c):
chapter_url = base_url + c.attrs.get('href') # 获取url
chapter_name = c.string # 获取章节名称
chapter_req = urllib.request.Request(chapter_url)
chapter_resp = urllib.request.urlopen(chapter_req, timeout=20)
chapter_content = chapter_resp.read()
chapter_soup = BeautifulSoup(chapter_content, 'html.parser')
# 查找章节部份内容
showtxt = chapter_soup.find_all(attrs={'class': 'showtxt'})
for txt in showtxt:
save_chapter(txt, save_dir + chapter_name + ".txt")
if __name__ == '__main__':
novel_list = get_chapter_url()
for chapter in novel_list:
get_chapter_content(chapter)
复制代码
以前就说过了,不少服务器都会限制ip访问的频度或者次数,能够经过设置代理 ip的方式来解决这个问题,代理ip百度一搜一堆,最出名的应该是西刺代理了: www.xicidaili.com/
爬虫中代理ip使用得很是频繁,每次都打开这个页面粘贴复制,感受 过于低端,并且还有个问题,代理ip是会过时失效的,并且不必定 一直能够用:要不来个这样的骚操做:
写个爬虫爬取代理ip列表,而后校验是否可用,把可用的存在本地, 下次须要代理的时候,读取这个文件中的ip,放到一个列表中,而后 轮流切换ip或者经过random模块随机取出一个,去访问目标地址。
抓取的网页是:www.xicidaili.com/nn/1
Network选项卡,Response看下页面结构,这里我喜欢在PyCharm上 新建一个HTML文件,结点可折叠,找关键位置代码很方便:
如图,不难发现就是咱们要找的内容,能够从 find_all(attrs={'id': 'ip_list'}) 这里入手,或者find_all('tr'),这里有个小细节的地方首项是相似于表头 的东西,咱们能够经过列表分片去掉第一个:find_all('tr')[1:] 此时的列表:
接着就是要拿出ip和端口了,遍历,先拿td,而后根据游标拿数据:
数据都拼接成"ip:端口号"的形式了,而后就是验证这个列表里的代理 是否均可以用了,验证方法也很简单,直接设置代理而后访问百度, 淘宝之类的网站,看返回码是否为200,是的话就表明代理可用,追加 到可用列表中,最后再把可用列表写入到文件中:
而后你就有本身的代理ip池了,要用的时候读取下文件就好,没事更新一波文件~ 有了代理ip池,和上面扒小说的程序可用结合一波,应该就不会出现503的问题了, 具体有兴趣的自行去完善吧(我懒...)
附上完整代码:
from bs4 import BeautifulSoup
import urllib.request
from urllib import error
test_url = "https://www.baidu.com/" # 测试ip是否可用
proxy_url = "http://www.xicidaili.com/nn/1" # ip抓取源
ip_file = "availableIP.txt"
# 把ip写入到文件中
def write_file(available_list):
try:
with open(ip_file, "w+") as f:
for available_ip in available_list:
f.write(available_ip + "\n")
except OSError as reason:
print(str(reason))
# 检测代理ip是否可用,返回可用代理ip列表
def test_ip(test_list):
available_ip_list = []
for test in test_list:
proxy = {'http': test}
try:
handler = urllib.request.ProxyHandler(proxy)
opener = urllib.request.build_opener(handler)
urllib.request.install_opener(opener)
test_resp = urllib.request.urlopen(test_url)
if test_resp.getcode() == 200:
available_ip_list.append(test)
except error.HTTPError as reason:
print(str(reason))
return available_ip_list
# 抓取西刺代理ip
def catch_ip():
ip_list = []
try:
# 要设置请求头,否则503
headers = {
'Host': 'www.xicidaili.com',
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36'
' (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36'
}
req = urllib.request.Request(proxy_url, headers=headers)
resp = urllib.request.urlopen(req, timeout=20)
content = resp.read()
soup = BeautifulSoup(content, 'html.parser')
catch_list = soup.find_all('tr')[1:]
# 保存代理ip
for i in catch_list:
td = i.find_all('td')
ip_list.append(td[1].get_text() + ":" + td[2].get_text())
return ip_list
except urllib.error.URLError as reason:
print(str(reason))
if __name__ == "__main__":
xici_ip_list = catch_ip()
available_ip_list = test_ip(xici_ip_list)
write_file(available_ip_list)
复制代码
结语:
本节学习了urllib库与Beautiful Soup,并经过两个很是简单的程序体验了 一波爬虫的编写,顺道温习了下上节的基础知识,美滋滋,比起Android 每天写界面,有趣太多,本节的东西仍是小儿科,下节咱们来啃硬骨头 正则表达式!敬请期待~
本节参考文献:
来啊,Py交易啊
想加群一块儿学习Py的能够加下,智障机器人小Pig,验证信息里包含: Python,python,py,Py,加群,交易,屁眼 中的一个关键词便可经过;
验证经过后回复 加群 便可得到加群连接(不要把机器人玩坏了!!!)~~~ 欢迎各类像我同样的Py初学者,Py大神加入,一块儿愉快地交流学♂习,van♂转py。