一、爬虫介绍
(一)爬虫的分类
1、通用爬虫:通用爬虫是搜索引擎(Baidu、Google、Yahoo等)“抓取系统”的重要组成部分。主要目的是将互联网上的网页下载到本地,形成一个互联网内容的镜像备份。再对这些网页做相关处理(提取关键字、去掉广告),最后提供一个用户检索接口。
搜索引擎如何抓取互联网上的网站数据?
- 门户网站主动向搜索引擎公司提供其网站的url
- 搜索引擎公司与DNS服务商合作,获取网站的url
- 门户网站主动挂靠在一些知名网站的友情链接中
2、聚焦爬虫:聚焦爬虫是根据指定的需求抓取网络上指定的数据。例如:获取豆瓣上电影的名称和影评,而不是获取整张页面中所有的数据值。
(二)Jupyter Notebook安装
1、Jupyter Notebook介绍
Jupyter Notebook是基于网页的用于交互计算的应用程序,可以在网页中直接编写代码、运行代码等。
2、安装
注:安装Python3.3及以上版本或2.7版本
需要使用Anaconda安装:
(1)去官网下载anaconda:https://www.anaconda.com/distribution/ 根据不同操作系统下载对应版本进行安装即可
配置环境变量后,可以通过命令启动:
jupyter notebook
进入如下界面:
你从哪个目录输入命令进到页面的,这个页面的主目录就是你输入命令的目录。
(2)使用介绍
目录下新建文件夹
勾选刚刚新建的文件夹,可以对其进行重命名:
点击进入这个文件夹,可以新建文件,如新建一个文本文件:
然后编写代码即可。
创建Python3源文件:
重命名后到目录中查看文件:
之后就可以在这里面进行爬虫项目编写。
(3)编写代码
执行标记类型的cell后将生成一个不可编辑的文档,如果想要继续编辑,就选中当前cell进行的双击即可:
效果:
标记类型的cell主要用于编写文档、HTML文件等 。
(4)常用快捷键
向上插入一个cell:a
向下插入一个cell:b
将cell类型切换为Markdown类型:m
将cell类型切换为code类型:y
执行cell:shift+enter
查看函数或模块的帮助文档:shift+tab
自动补全:tab
二、模块
(一)urllib模块
1、介绍
urllib是Python自带的一个用于爬虫的库,主要作用就是可以通过代码模拟浏览器发送请求。
常被用到的子模块在Python3中为 urllib.request 和 urllib.parse,在Python2中是 urllib 和 urllib2 。
使用流程:
- 指定url
- 基于urllib的request子模块发起请求
- 获取响应中的数据值
- 持久化存储
2、简单的爬取示例
- 爬取搜狗首页数据:
import urllib # 1、指定URL url = "https://www.sogou.com/" # 2、发送请求,获取响应对象 response = urllib.request.urlopen(url=url) # 3、获取页面数据 page_data = response.read() # read()返回的是byte类型的数据 # print(page_data) # 4、持久化数据 with open("./sougou.html", "wb") as f: f.write(page_data) print("successfully!")
- 爬取指定词条对应的页面数据
import urllib # 注意:url中不可以存在非ascii码的字符数据,在发送请求之前要对URL中的非ascii码的字符数据进行转码 query_data = urllib.parse.quote("爬虫") url = "https://www.sogou.com/web?query=%s" % query_data response = urllib.request.urlopen(url=url) page_data = response.read() with open("爬虫.html", "wb") as f: f.write(page_data)
3、反爬机制--UA身份伪装
User-Agent(UA):请求载体的身份标识
网站会检查UA,如果发现这个UA不是正常的浏览器发起的,则可能是个爬虫程序,那么网站可能就会拒绝提供数据。
相应的反反爬机制:伪装爬虫程序请求的UA
import urllib url = "https://www.baidu.com/" # 构造请求头相关信息 headers={"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.4325.121 Safari/694.36"} # 自定制请求对象 request = urllib.request.Request(url=url, headers=headers) # 针对自定制的请求对象发起请求 response = urllib.request.urlopen(request) print(response.read())
4、post请求
如何发起post请求爬取数据?如:在百度翻译中输入内容,获取翻译结果
先在浏览器中看一下,整个翻译过程的所有ajax请求中,哪个的请求头中携带了你所要翻译的文字:
可以在这个ajax请求里面看到有关请求信息:
接下来就可以模拟浏览器发起请求:
import urllib import json url = "https://fanyi.baidu.com/sug" # 将post参数封装成字典 data = {"kw": "爬虫"} # 编码请求数据进行编码处理 data = urllib.parse.urlencode(data) # 得到一个str类型 # 将str类型转换为byte类型 data = data.encode() # 发起post请求 response = urllib.request.urlopen(url=url, data=data) json_data = response.read() res = json.loads(json_data) print(res) # {'errno': 0, 'data': [{'k': '爬虫', 'v': '[pá chóng] reptile;'}]}
5、基于urllib模块的代理操作
代理,就是通过第三方代替本体处理相关事务,类似于代购,中介。
应用场景:一些网站会有相应的反爬虫措施,例如很多网站会检测某一段时间某个IP的访问次数,如果访问频率太快以至于看起来不像正常访客,它可能就会会禁止这个IP的访问。所以我们需要设置一些代理IP,每隔一段时间换一个代理IP,就算IP被禁止,依然可以换个IP继续爬取。
代理的分类:
- 正向代理:代理客户端获取数据。正向代理是为了保护客户端防止被追究责任。
- 反向代理:代理服务器提供数据。反向代理是为了保护服务器或负责负载均衡。
import urllib # 创建处理器对象,在其内部封装代理IP和port handler = urllib.request.ProxyHandler(proxies={"http": "192.168.23.233:8080"}) # 创建opener对象,使用该对象发起请求 opener = urllib.request.build_opener(handler) url = "https://www.baidu.com/s?ie=UTF-8&wd=python" headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36', } request = urllib.request.Request(url=url, headers=headers) response = opener.open(request) with open("proxy_data.html", "wb")as f: f.write(response.read())
6、基于urllib模块的cookie
有些网站需要有服务器返回给浏览器的cookie信息才能够获取数据,否则服务器返回的可能就是个登录页面。
所以我在爬取某些网站数据时,往往通过以下步骤来完成:
(1)使用爬虫程序登录一次网站,获取响应数据中的cookie
(2)再使用url进行请求时,携带 (1)中的cookie,进而获取到想要的数据
可以使用cookiejar来获取cookie:
cookiejar对象: - 作用:自动保存请求中的cookie数据信息 - 注意:必须和handler和opener一起使用 cookiejar使用流程: - 创建一个cookiejar对象 import http.cookiejar cj = http.cookiejar.CookieJar() - 通过cookiejar创建一个handler handler = urllib.request.HTTPCookieProcessor(cj) - 根据handler创建一个opener opener = urllib.request.build_opener(handler) - 使用opener.open方法去发送请求,且将响应中的cookie存储到openner对象中,后续的请求如果使用openner发起,则请求中就会携带了cookie
示例:爬取人人网的个人信息页面
import urllib from http import cookiejar cj = cookiejar.CookieJar() # 请求中的cookie会自动存储到cj对象中 handler = urllib.request.HTTPCookieProcessor(cj) opener = urllib.request.build_opener(handler) url = "http://www.renren.com/ajaxLogin/login?1=1&uniqueTimestamp=201873958471" # 自定义一个请求对象,让该对象作为opener的open函数中的参数 data={ "email":"[email protected]", "icode":"", "origURL":"http://www.renren.com/home", "domain":"renren.com", "key_id":"1", "captcha_type":"web_login", "password":"40dc65b82edd06d064b54a0fc6d202d8a58c4cb3d2942062f0f7dd128511fb9b", "rkey":"41b44b0d062d3ca23119bc8b58983104", "f":"https%3A%2F%2Fwww.baidu.com%2Flink%3Furl%3DpPKf2680yRLbbZMVdntJpyPGwrSk2BtpKlEaAuKFTsW%26wd%3D%26eqid%3Deee20f380002988c000000025b7cbb80" } data=urllib.parse.urlencode(data).encode() request=urllib.request.Request(url,data=data) opener.open(request) #获取当前用户的二级子页面 s_url='http://www.renren.com/289676607/profile' #该次请求中就携带了cookie response=opener.open(s_url) with open("renren.html", "wb")as f: f.write(response.read())
(二)requests模块
1、requests模块介绍
requests是Python中原生的基于网络请求的模块,用于模拟浏览器发起请求。
为什么使用requests模块?
- 使用urllib模块时,需要我们手动处理编码问题
- 使用urllib模块时,需要手动处理post请求参数
- 使用urllib模块处理cookie和代理操作的时候步骤比较繁琐
requests模块安装与使用:
# 安装 pip install requests #使用: -指定URL -使用requests模块发起请求 -获取响应数据 -持久化存储
2、基于requests模块的get请求
示例:
import requests url = "https://www.sogou.com/" response = requests.get(url=url) # 返回请求成功后的响应对象 # text属性:获取响应对象中的字符串形式的页面数据 page_data = response.text with open("sougou.html", "w", encoding="utf-8")as f: f.write(page_data)
response对象的其他属性:
import requests url = "https://www.sogou.com/" response = requests.get(url=url) # 返回请求成功后的响应对象 # content属性:获取响应对象中的byte类型的页面数据 page_data = response.content print(page_data)
import requests url = "https://www.sogou.com/" response = requests.get(url=url) # 返回请求成功后的响应对象 # status_code属性:获取响应对象中的状态码 page_data = response.status_code print(page_data)
import requests url = "https://www.sogou.com/" response = requests.get(url=url) # 返回请求成功后的响应对象 # headers属性:获取响应对象中的响应头信息 一个字典 page_data = response.headers print(page_data)
import requests url = "https://www.sogou.com/" response = requests.get(url=url) # 返回请求成功后的响应对象 # url属性:获取请求的URL page_data = response.url print(page_data)
携带参数的get请求:
# 方式一:
import requests url = "https://www.sogou.com/web?query=爬虫&ie=utf8" response = requests.get(url=url) # 自动处理URL中编码 page_data = response.text print(page_data)
# 方式二:将请求参数封装到字典中 import requests url = "https://www.sogou.com/web" params = {"query": "爬虫"} response = requests.get(url, params = params) print(response.text)
自定义请求头信息:
import requests url = "https://www.sogou.com/web" params = {"query": "爬虫"} headers = {"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3040.121 Safari/432.36"} response = requests.get(url, params = params, headers=headers) print(response.status_code)
3、基于requests模块的post请求
需求:只有登录成功后才能获取页面数据
import requests url = "https://accounts.douban.com/login" # 找到post请求所对应的URL data = { "source": "movie", "redir": "https://movie.douban.com", "form_email": "15124567889", "form_password": "123456789", "login": "登录" } headers = { "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/323.36 (KHTML, like Gecko) Chrome/72.0.4232.121 Safari/537.36" } response = requests.post(url=url, data=data, headers=headers) with open("douban.html", "wb")as f: f.write(response.content)
4、基于requests模块的ajax的get请求
# 获取豆瓣电影的分类信息 import requests url = "https://movie.douban.com/j/new_search_subjects" params = { "sort": "U", "range": [0,10], "tags": "", "start": "0", "countries": "香港" } headers = { "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36" } response = requests.get(url=url, params=params, headers=headers) page_data = response.text print(page_data)
返回的结果是json字符串。
5、基于requests模块的ajax的post请求
# 查询肯德基的餐厅信息 import requests url = "http://www.kfc.com.cn/kfccda/ashx/GetStoreList.ashx?op=keyword" data = { "cname": "", "pid": "", "keyword": "上海", "pageIndex": "2", "pageSize": "10" } headers = { "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36" } response = requests.post(url, headers=headers, data=data) page_data = response.text # 一组json字符串 print(page_data)
6、示例
# 爬取搜狗知乎某个词条对应一定页码范围的数据 import requests import os if not os.path.exists("./sougou_pages"): os.mkdir("./sougou_pages") url = "https://zhihu.sogou.com/zhihu" # 检索字段 words = input("输入要检索的词条:").strip() # 动态指定页码范围 start_num = int(input("输入起始页码:")) end_num = int(input("输入结束页码:")) headers = { "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36" } # 通过循环获取指定页码范围的数据 for i in range(start_num, end_num+1): params = { "query":words, "page": i, "ie": "utf8" } response = requests.get(url, params=params, headers=headers) page_text = response.text file_name = "sougou_page_data_%s.html" % str(i) # 文件名 file_path = os.path.join("./sougou_pages", file_name) # 文件路径 with open(file_path, "w", encoding="utf-8")as f: f.write(page_text) print("第%d页写入成功" % i)
爬取百度贴吧指定页码的数据
爬取维基百科指定页码的数据
7、requests模块的cookie操作
当我们点击“登录”按钮,登录成功之后,服务端会为该次请求创建一个cookie,并且将这个cookie发送给客户端,客户端将其进行存储
import requests # 发起登录请求,将获取到的cookie存入session对象中 post_url = "https://accounts.douban.com/j/mobile/login/basic" data = { "ck": "", "name": "15523455432", "password": "123456789", "remember": "false", "ticket": "5uFnV1iD7YieacEl8MnYS67-sMYiRMAw3qK42WWk29CgL0Ll2bNrfFRtR7N7of985bVPu17aUU4*" } headers = { "Referer": "https://accounts.douban.com/passport/login?source=movie", "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/342.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36", "X-Requested-With": "XMLHttpRequest" } # 创建session对象 session = requests.session() # 登录请求:获取cookie login_response = session.post(url=post_url, data=data, headers=headers) # 二次请求携带cookie:获取数据 url = "https://www.douban.com/" response = session.get(url, headers=headers) page_text = response.text with open("douban_personal.html", "w", encoding="utf-8")as f: f.write(page_text) print("successfully")
8、requests模块的代理操作
import requests url = "https://www.baidu.com/s?ie=utf-8&wd=IP" headers = { "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36" } # 将代理IP封装到字字典中 代理IP要与实际IP的协议类型相同,比如都使用http,或都使用https proxy_ip = {"https": "114.88.53.19:53281"} # 更换网络IP response = requests.get(url=url, headers=headers, proxies=proxy_ip) page_text = response.text with open("proxy_page.html", "w", encoding="utf-8")as f: f.write(page_text) print("successfully")
9、验证码处理
云打码平台自动识别验证码流程:
(1)对携带验证码的页面数据进行抓取
(2)解析页面数据中的验证码,将验证码图片下载到本地
(3)将验证码图片发送给第三方平台处理,然后返回图片上的数据
云打码平台http://www.yundama.com/demo.html
注册:用户注册和开发者注册都要
登录:登录开发者平台
- 示例代码下载(在开发者首页->开发文档->调用示例即最新DLL->PythonHTTP示例下载),将下载的压缩包解压,里面有三个文件
- 新建一个软件(在开发者首页->我的软件->添加新软件->输入软件名称,点击提交即可
使用刚刚下载的示例代码中源码文件中的代码进行修改(YDMHTTPDemo3.x.py),让其识别验证码图片的数据值:
- 拷贝YDMHTTPDemo3.x.py文件中的类到你的cell中,运行一下cell将类加载到当前源文件中
- 拷贝YDMHTTPDemo3.x.py文件中余下的代码,新建一个cell,在里面新建一个函数,将拷贝的代码写到函数中,修改里面的用户名、密码等配置信息(记得以普通用户身份登录打码平台充值后才能进行验证码识别)
import http.client, mimetypes, urllib, json, time, requests class YDMHttp: apiurl = 'http://api.yundama.com/api.php' username = '' password = '' appid = '' appkey = '' def __init__(self, username, password, appid, appkey): self.username = username self.password = password self.appid = str(appid) self.appkey = appkey def request(self, fields, files=[]): response = self.post_url(self.apiurl, fields, files) response = json.loads(response) return response def balance(self): data = {'method': 'balance', 'username': self.username, 'password': self.password, 'appid': self.appid, 'appkey': self.appkey} response = self.request(data) if (response): if (response['ret'] and response['ret'] < 0): return response['ret'] else: return response['balance'] else: return -9001 def login(self): data = {'method': 'login', 'username': self.username, 'password': self.password, 'appid': self.appid, 'appkey': self.appkey} response = self.request(data) if (response): if (response['ret'] and response['ret'] < 0): return response['ret'] else: return response['uid'] else: return -9001 def upload(self, filename, codetype, timeout): data = {'method': 'upload', 'username': self.username, 'password': self.password, 'appid': self.appid, 'appkey': self.appkey, 'codetype': str(codetype), 'timeout': str(timeout)} file = {'file': filename} response = self.request(data, file) if (response): if (response['ret'] and response['ret'] < 0): return response['ret'] else: return response['cid'] else: return -9001 def result(self, cid): data = {'method': 'result', 'username': self.username, 'password': self.password, 'appid': self.appid, 'appkey': self.appkey, 'cid': str(cid)} response = self.request(data) return response and response['text'] or '' def decode(self, filename, codetype, timeout): cid = self.upload(filename, codetype, timeout) if (cid > 0): for i in range(0, timeout): result = self.result(cid) if (result != ''): return cid, result else: time.sleep(1) return -3003, '' else: return cid, '' def report(self, cid): data = {'method': 'report', 'username': self.username, 'password': self.password, 'appid': self.appid, 'appkey': self.appkey, 'cid': str(cid), 'flag': '0'} response = self.request(data) if (response): return response['ret'] else: return -9001 def post_url(self, url, fields, files=[]): for key in files: files[key] = open(files[key], 'rb'); res = requests.post(url, files=files, data=fields) return res.text def get_code(code_img_path): """该函数调用了打码平台的相关接口,对指定验证码图片进行识别,返回图片上的数据""" # 云打码平台普通用户的用户名 username = '用户名' # 密码 password = '密码' # 软件ID(软件代码),开发者分成必要参数。登录开发者后台【我的软件】获得! appid = ID # 软件密钥,开发者分成必要参数。登录开发者后台【我的软件】获得! appkey = '秘钥' # 图片文件 filename = code_img_path # 验证码类型,# 例:1004表示4位字母数字,不同类型收费不同。请准确填写,否则影响识别率。在此查询所有类型 http://www.yundama.com/price.html codetype = 1004 # 超时时间,秒 timeout = 20 # 检查 if (username == 'username'): print('请设置好相关参数再测试') else: # 初始化 yundama = YDMHttp(username, password, appid, appkey) # 登陆云打码 uid = yundama.login(); print('uid: %s' % uid) # 查询余额 balance = yundama.balance(); print('balance: %s' % balance) # 开始识别,图片路径,验证码类型ID,超时时间(秒),识别结果 cid, result = yundama.decode(filename, codetype, timeout); print('cid: %s, result: %s' % (cid, result)) return result import requests from lxml import etree # 获取带有验证码图片的页面 url = "http://www.cdpta.gov.cn/frt/student/login.do" headers = { "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36" } session = requests.session() page_text = session.get(url, headers=headers).text # 解析页面的验证码 tree = etree.HTML(page_text) # 去网页中复制验证码图片的xpath 如果需要的话再通过相关处理获取到图片的URL code_img_url = tree.xpath('//*[@id="yzmimg"]/@src')[0] code_img_url = "http://www.cdpta.gov.cn" + code_img_url.split(";")[0] # 获取图片的二进制数据 code_img = session.get(url=code_img_url, headers=headers).content # 保存图片到本地 with open("code_img.png", "wb")as f: f.write(code_img) print("successfully") # 调用函数识别图片验证码 res = get_code("./code_img.png") print("验证码字符:", res) # 进行登录操作 login_url = "http://www.cdpta.gov.cn/frt/student/login.do" data = { "name": "xxx", "idcard": "xxx", "password": "xxx", "yzm": res, "newpassword": "", "newpassword2": "", "photofile": "(binary)", "findpassword_mo": "将密码直接发送到注册的手机", "loginction": "登录" } response = session.post(url=login_url, data=data, headers=headers) page_content = response.content with open("cdrs.html", "wb")as f: f.write(page_content) print("successfully")
三、数据解析
一般在持久化存储爬取的数据之前会进行数据解析,留下有用的部分。
(一)正则解析
1、正则匹配规则:
单字符: . : 除换行以外所有字符 [] :[aoe] [a-w] 匹配集合中任意一个字符 \d :数字 [0-9] \D : 非数字 \w :数字、字母、下划线、中文 \W : 非\w \s :所有的空白字符包,括空格、制表符、换页符等等。等价于 [ \f\n\r\t\v]。 \S : 非空白 数量修饰: * : 任意多次 >=0 + : 至少1次 >=1 ? : 可有可无 0次或者1次 {m} :固定m次 hello{3,} {m,} :至少m次 {m,n} :m-n次 边界: $ : 以某某结尾 ^ : 以某某开头 分组: (ab) 贪婪模式: .* 非贪婪(惰性)模式: .*? re.I : 忽略大小写 re.M :多行匹配 re.S :单行匹配 re.sub(正则表达式, 替换内容, 字符串)
2、使用:
import re # 提取python s = "javapythongo+c#" re.findall("python", s) # 返回一个列表 ['python'] # 提取hello world s = "<html><h3>hello world</h3><html>" re.findall("<h3>(hello world)</h3>", s)[0] # 利用分组 # 提取123 s = "哈哈哈123嘻嘻嘻" re.findall("\d+", s)[0] # 提取http:// 和 https:// s = "http://www.baidu.com and https://www.baidu.com and httpss://" # re.findall("https?://", s) # ?表示前面的字符出现0次或1次 re.findall("https{0,1}://", s) # {}表示前面的字符出现0次或1次 # 提取qq. s = "[email protected]" # re.findall("q.*\.", s) # 贪婪模式 ['qq.cn.'] re.findall("q.*?\.", s) # 非贪婪模式 # 匹配abc 和 abccc s = "abc abcc abccc abcd" re.findall("abc{1,3}", s) # 匹配a开头的行 re.S(基于单行匹配) re.M(基于多行匹配) s = """ this is a book which is on the desk. application plant trees """ re.findall("^a.*", s, re.M) # 匹配所有行 s = """<div> 轻轻原上草, 一岁一枯荣。 野火烧不尽, 春风吹又生。 </div> """ re.findall("<div>.*</div>", s, re.S)
3、示例:使用正则对糗事百科中数据进行解析并下载
import re import requests import os # 1、指定url url = "https://www.qiushibaike.com/pic/" # 2、发起请求 response = requests.get(url) # 3、获取页面数据 page_text = response.text # 4、解析数据 """ 在浏览器中查看源码找到图片对应的标签: <div class="thumb"> <a href="/article/121627410" target="_blank"> <img src="//pic.qiushibaike.com/system/pictures/12162/121627410/medium/D17IKNXRXJLDYTJ3.jpg" alt="image"> </a> </div> 通过解析img标签的src属性即可获得图片链接 """ img_link_list = re.findall('<div class="thumb">.*?<img src="(.*?)".*?>.*?</div>', page_text, re.S) # 完整的图片链接地址 https://pic.qiushibaike.com/system/pictures/12162/121627410/medium/D17IKNXRXJLDYTJ3.jpg # 创建一个文件夹用于存放获取的图片 if not os.path.exists("./choushibaike_imgs"): os.mkdir("choushibaike_imgs") for url in img_link_list: # 给获取的图片链接拼接完整的链接地址 img_url = "https:" + url # 发起请求获取图片二进制数据 img_data = requests.get(url=img_url).content img_name = url.split("/")[-1] # 或 re.findall("\//.*?medium\/(.*)", url) # 5、持久化存储数据 with open(os.path.join("./choushibaike_imgs", img_name), "wb")as f: f.write(img_data) print("successfully!")
4、爬取指定范围页码的图片:
import re import requests import os # 1、指定url url = "https://www.qiushibaike.com/pic/page/" # https://www.qiushibaike.com/pic/page/3/ # 2、发起请求 start_page = int(input("输入起始页码:")) end_page = int(input("输入结束页码:")) page_text = "" for i in range(start_page, end_page + 1): response = requests.get(url=url + str(i) + "/") # 3、获取页面数据 page_text = page_text + response.text # 4、解析数据 img_link_list = re.findall('<div class="thumb">.*?<img src="(.*?)".*?>.*?</div>', page_text, re.S) # 完整的图片链接地址 https://pic.qiushibaike.com/system/pictures/12162/121627410/medium/D17IKNXRXJLDYTJ3.jpg # 创建一个文件夹用于存放获取的图片 if not os.path.exists("./choushibaike_imgs"): os.mkdir("choushibaike_imgs") for url in img_link_list: # 给获取的图片链接拼接完整的链接地址 img_url = "https:" + url # 发起请求获取图片二进制数据 img_data = requests.get(url=img_url).content img_name = url.split("/")[-1] # 或 re.findall("\//.*?medium\/(.*)", url) # 5、持久化存储数据 with open(os.path.join("./choushibaike_imgs", img_name), "wb")as f: f.write(img_data) print("successfully!")
(二)xpath解析
1、如何使用
(1)下载lxml:pip install lxml
(2)导入包:form lxml import etree
(3)创建etree对象进行指定数据的解析,此时有两种情况:
- 如果解析的数据是来源于本地:
etree = etree.parse("本地文件路径") etree.xpath("xpath表达式")
- 如果数据来源于网络:
etree = etree.HTML("网络请求到的页面数据") etree.xpath("xpath表达式")
2、常用xpath解析式:
xpath函数返回的结果是一个列表。
# 属性定位: 找到class属性值为song的div标签 //div[@class="song"] # 层级&索引定位: 找到class属性值为tang的div的直系子标签ul下的第二个子标签li下的# # 直系子标签a //div[@class="tang"]/ul/li[2]/a # 逻辑运算: 找到href属性值为空且class属性值为du的a标签 //a[@href="" and @class="du"] # 模糊匹配: //div[contains(@class, "ng")] //div[starts-with(@class, "ta")] # 取文本: /表示获取某个标签下的文本内容 //表示获取某个标签下的文本内容和所有子标签下的文本内容 //div[@class="song"]/p[1]/text() //div[@class="tang"]//text() # 取属性: //div[@class="tang"]//li[2]/a/@href
3、本地文件解析
建立一个本地测试文件test_file.html:
<html lang="en"> <head> <meta charset="UTF-8" /> <title>本地测试文件</title> </head> <body> <div> <p>百里守约</p> </div> <div class="song"> <p>李清照</p> <p>王安石</p> <p>苏轼</p> <p>柳宗元</p> <a href="http://www.song.com/" title="赵匡胤" target="_self"> <span>this is span</span> 宋朝是最强大的王朝,不是军队的强大,而是经济很强大,国民都很有钱</a> <a href="" class="du">总为浮云能蔽日,长安不见使人愁</a> <img src="http://www.baidu.com/meinv.jpg" alt="" /> </div> <div class="tang"> <ul> <li><a href="http://www.baidu.com" title="qing">清明时节雨纷纷,路上行人欲断魂,借问酒家何处有,牧童遥指杏花村</a></li> <li><a href="http://www.163.com" title="qin">秦时明月汉时关,万里长征人未还,但使龙城飞将在,不教胡马度阴山</a></li> <li><a href="http://www.126.com" alt="qi">岐王宅里寻常见,崔九堂前几度闻,正是江南好风景,落花时节又逢君</a></li> <li><a href="http://www.sina.com" class="du">杜甫</a></li> <li><a href="http://www.dudu.com" class="du">杜牧</a></li> <li><b>杜小月</b></li> <li><i>度蜜月</i></li> <li><a href="http://www.haha.com" id="feng">凤凰台上凤凰游,凤去台空江自流,吴宫花草埋幽径,晋代衣冠成古丘</a></li> </ul> </div> </body> </html>
解析:
from lxml import etree # 创建etree对象 etr = etree.parse("./test_file.html") # 找到class属性值为song的div标签 etr.xpath('//div[@class="song"] ') # 找到class属性值为tang的div的直系子标签ul下的第二个子标签li下的直系子标签a etr.xpath('//div[@class="tang"]/ul/li[2]/a') # 找到href属性值为空且class属性值为du的a标签 etr.xpath('//a[@href="" and @class="du"]') # 模糊匹配 class属性值包含“ng”的div标签 etr.xpath('//div[contains(@class, "ng")]') # 模糊匹配 class属性值以“ta”开头的div标签 etr.xpath('//div[starts-with(@class, "ta")]') # /表示获取某个标签下的文本内容 etr.xpath('//div[@class="song"]/p[1]/text()') # //表示获取某个标签下的文本内容和所有子标签下的文本内容 etr.xpath('//div[@class="tang"]//text()') # 获取class属性为tang的div标签下的第二个li标签下的a标签的href属性值 etr.xpath('//div[@class="tang"]//li[2]/a/@href')
4、xpath插件 xpath.crx
安装xpath插件在浏览器中对xpath表达式进行验证:可以在插件中直接执行xpath表达式
以谷歌浏览器为例:
打开浏览器->更多工具->扩展程序->开启开发者模式->将xpath插件拖到这个页面中
启动和关闭插件的快捷键: ctrl + shift + x
5、示例:对段子网中的段子标题和内容进行解析并存储
import requests from lxml import etree url = "https://ishuo.cn/joke" response = requests.get(url) page_text = response.text etr = etree.HTML(page_text) # 获取页面所有的li标签 li_list = etr.xpath('//div[@id="list"]/ul/li') # Element类型的对象可以继续调用xpath函数,对该对象表示的局部内容进行指定内容的解析 f = open("duanzi.txt", "w", encoding="utf-8") for li in li_list: title = li.xpath('./div[@class="info"]/a/text()')[0] content = li.xpath('./div[@class="content"]/text()')[0] f.write(title + ":" + content + "\n\n") f.close() print("ok")
(三)BeautifulSoup解析
BeautifulSoup是Python独有的解析方式,简单、高效。
1、安装
- 需要将pip源设置为国内源,阿里源、豆瓣源、网易源等 - windows (1)打开文件资源管理器(文件夹地址栏中) (2)地址栏上面输入 %appdata% (3)在这里面新建一个文件夹 pip (4)在pip文件夹里面新建一个文件叫做 pip.ini ,内容写如下即可: [global] timeout = 6000 index-url = https://mirrors.aliyun.com/pypi/simple/ trusted-host = mirrors.aliyun.com - linux (1)cd ~ (2)mkdir ~/.pip (3)vi ~/.pip/pip.conf (4)编辑内容,和windows一模一样 - 需要安装: bs4在使用时候需要一个第三方库,把这个库也安装一下 pip install lxml 安装bs4 pip install bs4
2、使用流程
BeautifulSoup可以将一个html文档转换为一个BeautifulSoup对象,调用该对象中属性和方法进行html文档指定内容的定位查找。
(1)导包:from bs4 import BeautifulSoup
(2)将一个html文档,转化为BeautifulSoup对象,然后通过对象的方法或者属性去查找指定的节点内容,存在两种情况:
转化本地文件:
soup = BeautifulSoup(open('本地文件'), 'lxml')
转化网络文件:
soup = BeautifulSoup('网络请求到的字符串类型或者字节类型的数据', 'lxml')
常用属性和方法:
(1)根据标签名查找 - soup.a 只能找到第一个符合要求的标签 (2)获取属性 - soup.a.attrs 获取a所有的属性和属性值,返回一个字典 - soup.a.attrs['href'] 获取href属性 - soup.a['href'] 也可简写为这种形式 (3)获取内容 - soup.a.string - soup.a.text - soup.a.get_text() 【注意】如果标签还有标签,那么string获取到的结果为None,而其它两个,可以获取文本内容 (4)find:找到第一个符合要求的标签 - soup.find('a') 找到第一个符合要求的 - soup.find('a', title="xxx") - soup.find('a', alt="xxx") - soup.find('a', class_="xxx") - soup.find('a', id="xxx") (5)find_all 或者 findAll:找到所有符合要求的标签 - soup.find_all('a') - soup.find_all(['a','b']) 找到所有的a和b标签 - soup.find_all('a', limit=2) 限制前两个 (6)根据选择器选择指定的内容 select:soup.select('#feng') - 常见的选择器:标签选择器(a)、类选择器(.)、id选择器(#)、层级选择器 - 层级选择器: div .dudu #lala .meme .xixi 下面好多级 div > p > a > .lala 只能是下面一级 【注意】select选择器返回永远是列表,需要通过下标提取指定的对象
# 对一个本地文档进行解析 from bs4 import BeautifulSoup f = open('./test_file.html', 'r') soup = BeautifulSoup(f, 'lxml') # 查找p标签 只能找到第一个p标签 print(soup.p) # 获取a标签的属性值,返回一个字典 print(soup.a.attrs) # 获取内容 print(soup.p.string) print(soup.p.text) # find:查找第一个符合要求的标签 print(soup.find('a', class_="du")) # find_all:找到所有符合要求的标签 print(soup.find_all("p")) # print(soup.findAll("p")) # select函数:选择器 返回的是一个列表 print(soup.select("div > img")) print(soup.select(".tang li")) f.close()
3、示例:爬取古诗文网中三国小说里的标题和内容
import requests from bs4 import BeautifulSoup url = "http://www.shicimingju.com/book/sanguoyanyi.html" def get_content(url): # 发起请求,获取数据 page_text = requests.get(url).text soup = BeautifulSoup(page_text, "lxml") return soup soup = get_content(url) a_list = soup.select(".book-mulu > ul > li > a") # 所有a标签对象 # print(type(a)) # <class 'bs4.element.Tag'> # Tag类型的对象可以继续调用相应的属性和方法进行数据解析 f = open("sanguoyanyi.txt", "w", encoding="utf-8") for a in a_list: title = a.text # 章节对应的完整地址:http://www.shicimingju.com/book/sanguoyanyi/1.html a_link = "http://www.shicimingju.com" + a["href"] soup = get_content(a_link) contents = soup.find("div", class_ = "chapter_content") content = contents.text f.write(title + content + "\n\n") f.close() print("ok")
四、selenium和phantomJs处理网页动态加载数据的爬取
有些网站的页面数据是随着鼠标往下滑动才动态的加载出来的,这种情况下,我们可以借助一些工具进行处理。
(一)selenium
1、介绍
selenium是一个第三方库,可以实现让浏览器自动化的操作
2、环境搭建
(1)安装:pip install selenium
(2)获取浏览器的驱动程序
- 谷歌浏览器驱动下载地址:
http://chromedriver.storage.googleapis.com/index.html
- 下载的驱动程序必须和浏览器的版本统一,可以在这里查看:http://blog.csdn.net/huilan_same/article/details/51896672
- 将下载好的驱动解压,拷贝到当前工作目录下
3、使用
# 1、导包 from selenium import webdriver # 2、创建一个浏览器对象 browser = webdriver.Chrome(executable_path='驱动路径') # 3、使用浏览器发起指定请求 browser.get(url) # 使用下面的方法,查找指定的元素进行操作即可 find_element_by_id 根据id找节点 find_elements_by_name 根据name找 find_elements_by_xpath 根据xpath查找 find_elements_by_tag_name 根据标签名找 find_elements_by_class_name 根据class名字查找
4、示例:在百度进行指定词条的搜索
from selenium import webdriver import time # 创建一个浏览器对象 browser = webdriver.Chrome(executable_path='./chromedriver') # 使用浏览器发起指定请求 browser.get("https://www.baidu.com") time.sleep(1) # 停留1秒(模拟人工操作) text = browser.find_element_by_id("kw") # 通过百度输入框的id进行定位 text.send_keys("爬虫") # 输入检索字段:爬虫 time.sleep(1) button = browser.find_element_by_id("su") # 定位提交按钮 button.click() # 点击按钮 time.sleep(6) browser.quit() # 关闭浏览器
(二)phantomJs
phantomJs是一款无界面的浏览器,使用方法与谷歌驱动差不多,先下载驱动,拷贝到当前项目目录以备用
import time from selenium import webdriver browser = webdriver.PhantomJS(executable_path="./phantomjs-2.1.1-macosx/bin/phantomjs") # 导入驱动路径 # 打开浏览器 browser.get("https://www.baidu.com") # 截屏保存 browser.save_screenshot("./001打开浏览器.png") time.sleep(1) # 定位文本框 输入检索词条 text = browser.find_element_by_id("kw") text.send_keys("人工智能") browser.save_screenshot("./002输入词条.png") time.sleep(1) # 定位提交按钮 button = browser.find_element_by_id("su") button.click() browser.save_screenshot("./003检索结果.png") # 关闭浏览器 time.sleep(4) browser.quit() # 注意:selenium 3.x版本已经不支持PhantomJS,可以下载selenium 2.x版本 pip install selenium==2.48.0