网络爬虫集合【看这一篇就够了】

发布时间:2025-12-09 20:31:25 浏览次数:4

文章目录

    • (一)爬虫初始
    • (二)数据请求
      • 2.0、urllib模块
          • 基本使用
          • 一个类型和6和方法
          • 下载
          • 请求对象的定制
          • 编/解码
          • ajax的get请求
          • ajax的post请求
          • URLError/HTTPError
          • Handler处理器
          • 代理服务器
      • 2.1、requests模块
        • 第一个爬虫程序
        • requests模块实战
    • (三)数据解析
        • 3.0概述
        • 3.1正则表达式
        • 3.2re模块
        • 3.3 bs4数据解析
        • 3.4xpath数据解析
        • 3.5jsonpaht数据解析
    • (四)爬虫进阶
        • 4.0验证码识别
        • 4.1 cookie
        • 4.2代理
        • 4.3防盗链
    • (五)异步爬虫
        • 5.0单线程
        • 5.1多线程
        • 5.2多进程
        • 案例:基于进程+线程实现多任务爬虫
        • 5.3线程池和进程池
        • 5.4协程
          • 5.4.1async & await关键字
          • 5.4.2事件循环
          • 5.4.3快速上手
          • 5.4.4await关键字
          • 5.4.5Task对象
          • 5.4.6async.Future对象
          • 5.4.7concurrent.futures.Future对象
          • 5.4.8异步和非异步
          • 5.4.9异步上下文管理器
        • 5.5 多任务异步协程
        • 5.6aiohttp模块
    • (六)动态加载数据处理
        • selenium
          • 6.0selenium模块使用
          • 6.1其他自动化操作
          • 6.2拉钩网自动化操作
          • 6.3窗口切换
          • 6.4动作链和iframe的处理
          • 6.5模拟登录qq空间
        • Phantomjs
        • Chrome handless
          • 固定配置
          • 代码封装
          • 谷歌无头浏览器+反检测
    • (七)scrapy框架
        • scrapy创建以及运行
        • scrapy架构组成
          • 五大核心组件
        • scrapy工作原理
        • scrapy shell
        • yield
        • 解析相关
        • 应用
          • 1.1scrapy持久化存储:
            • 基于本地存储
            • 基于管道存储
            • 开启多条管道
          • 1.2基于Spider的全站数据解析爬取
          • 1.3请求传参
        • 图片管道
          • 图片数据爬取之自定义ImagePipeline
        • CrawlSpider
        • CrawlSpider案例
        • 日志信息和等级
        • scrapy的post请求
    • 分布式爬虫

(一)爬虫初始

通过编写程序,模拟浏览器去上网,然后让其去互联网上抓取数据的过程

爬虫分类

  • 通用爬虫 :抓取系统重要组成部分,抓取一整张页面的数据
  • 聚焦爬虫:建立在通用爬虫基础之上,抓取的是页面中特定的局部内容
  • 增量式爬虫 :检测网站中数据更新的情况,只会抓取网站中最新更新出来的数据

爬虫的矛与盾

  • 反爬机制: 门户网站,可以通过制定相应的策略或者技术手段,防止爬虫程序进行网站数据的爬取

  • 反反爬策略: 爬虫程序可以通过指定相关策略或者技术手段,破解门户网站中具备的反爬机制,从而可以获取门户网站中相关的数据

robots.txt协议:

  • 君子协议,规定了网站中哪些数据可以被爬虫爬取,哪些数据不可以被爬虫爬取

    #查看淘宝网站的robots.txt协议www.taobao.com/robots.txt

HTTP&HTTPS协议

  • 当前URL地址在数据传输的时候遵循的HTTP协议

  • 协议:就是两个计算机之间为了能够流畅的进行沟通而设置的一个君子协议,常见的协议有TCP/IP SOAP协议,HTTP协议, SMTP协议等…

  • HTTP协议: Hyper Text Transfer Protocol(超文本传输协议) 的缩写,是用于万维网服务器传输超文本到浏览器的传送协议

  • HTTPS协议:安全的超文本传输协议

  • 直白点,就是浏览器和服务器之间的数据交互遵守的HTTP协议

  • HTTP协议把一条消息分为三大快内容,无论是请求还是响应的三块内容

# 加密方式:# 对称密钥加密,# 非对称密钥加密,# 证书密钥加密 # 请求:'''1.请求行--》请求方法(get/post) 请求url地址,协议2.请求头--》放一些服务器要使用的附加信息34.请求体--》一般放一些参数''' #请求头中常见的一些重要内容(爬虫需要)'''1.USer-Agent:请求载体的身份标识(用啥发送的请求)2.Refer:防盗链(这次请求是从哪些页面来的? 反爬会用到)3.cookie:本地字符串数据信息(用户登录信息,反爬的token)Connection:请求完毕后,是保持连接还是断开连接''' #响应:'''1.状态行--》协议 状态码 (200 404 500)2.响应头--》放一些客户端要使用的一些附加信息3.4.响应体--》服务器返回的真正客户端要使用的内容(HTML,json)等''' #响应头中一些重要内容'''Content-Type:服务器响应回客户端的数据类型1.cookie:本地字符串数据信息(用户登录信息,反爬的token)2.各种神奇的莫名其妙的字符串(这个需要经验了,一般都是token字样,防止各种攻击和反爬)''' #请求方式:''''GET:显示提交POST:隐示提交'''

反扒手段

(二)数据请求

作用:模拟浏览器 发起请求

2.0、urllib模块

基本使用
# 使用urllib获取百度源码import urllib.request# 1. 定义一个urlurl = 'http://www.baidu.com'# 2. 模拟浏览器向服务器发送请求response = urllib.request.urlopen(url)# 3. 获取响应中的页面源码content=response.read().decode('utf-8') #read方法返回字节形式的二进制数据# 4. 打印数据print(content)
一个类型和6和方法
import urllib.requesturl = 'http://www.baidu.com'response = urllib.request.urlopen(url)# 一个类型和6个方法print(type(response)) #<class 'http.client.HTTPResponse'># content=response.read()# content = response.read(5) # 返回5个字节# content = response.readline() # 读取一行# content = response.readlines() # 读取多行# print(content)print(response.getcode())#返回状态码print(response.geturl()) #返回url地址print(response.getheaders()) #获取状态信息
下载
import urllib.request# 下载网页# url_html = 'http://www.baidu.com'# urllib.request.urlretrieve(url=url_html, filename='../data/百度.html')# 下载图片# url_img='https://gitee.com/zh_sng/cartographic-bed/raw/master/img/image-20230202185000596.png'# urllib.request.urlretrieve(url_img,'../data/girl.png')# 下载视频# url_vedio='http://vd3.bdstatic.com/mda-pb158hh9ss4ev38n/cae_h264/1675309740554567465/mda-pb158hh9ss4ev38n.mp4'# urllib.request.urlretrieve(url_vedio,'../data/vedio.mp4')
请求对象的定制
import urllib.requesturl = 'https://www.baidu.com'headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36'}# 请求对象的定制 ,urlopen中不能存储字典,request=urllib.request.Request(url=url,headers=headers)response = urllib.request.urlopen(request)content = response.read().decode('utf-8')print(content)
编/解码
  • get请求的quote方法
  • import urllib.requestimport urllib.parse# https://www.baidu.com/s?ie=UTF-8&wd=%E5%91%A8%E6%9D%B0%E4%BC%A6url = 'https://www.baidu.com/s?wd='headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36'}#将周杰伦三个字变成unicode编码格式name=urllib.parse.quote('周杰伦')url += url+namerequest = urllib.request.Request(url=url, headers=headers)response = urllib.request.urlopen(request)content = response.read().decode('utf-8')print(content)
  • get请求的urlencode方法
  • import urllib.requestimport urllib.parse# urlencode应用场景:多个参数的时候# https://www.baidu.com/s?wd=周杰伦&sex=男base_url = 'https://www.baidu.com/s?'data = {'wd': '周杰伦','sex': '男'}data = urllib.parse.urlencode(data)url = base_url + dataheaders = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36'}# 请求对象的定制request = urllib.request.Request(url=url, headers=headers)# 模拟浏览器箱服务器发送请求response = urllib.request.urlopen(request)content = response.read().decode('utf-8')print(content)
  • post请求百度翻译
  • import urllib.requestimport urllib.parse# https://fanyi.baidu.com/langdetecturl = 'https://fanyi.baidu.com/langdetect'# 请求头headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36'}data = {'query': 'Python'}# post请求参数必须要进行编码data = urllib.parse.urlencode(data).encode('utf-8')# post请求的参数不会拼接在url后面,而是放置在请求对象定制的参数中request = urllib.request.Request(url=url, data=data, headers=headers)# 模拟浏览器向服务器发送请求response = urllib.request.urlopen(request)#获取相应的数据content=response.read().decode('utf-8')print(content)
  • post请求百度翻译之详细翻译
  • import urllib.requestimport urllib.parseurl = 'https://fanyi.baidu.com/v2transapi?from=en&to=zh'headers = {'Cookie': "BIDUPSID=563BF61659E928EF78958A45A41BEC59; PSTM=1673672643; BAIDUID=563BF61659E928EFB7C9D1115AAAF5FF:FG=1; BDUSS=Fd3RkpzZ3NSWlhTcmRUbDN4VEc5WXR4U2ZmZDN0VmUtcUcwY2ZIUXdIZFFhT3RqRVFBQUFBJCQAAAAAAAAAAAEAAACEELRO1dTLzNChza~QrAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFDbw2NQ28Nja; BDUSS_BFESS=Fd3RkpzZ3NSWlhTcmRUbDN4VEc5WXR4U2ZmZDN0VmUtcUcwY2ZIUXdIZFFhT3RqRVFBQUFBJCQAAAAAAAAAAAEAAACEELRO1dTLzNChza~QrAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFDbw2NQ28Nja; BDORZ=B490B5EBF6F3CD402E515D22BCDA1598; H_PS_PSSID=36558_38113_38093_37140_37906_37989_36807_37923_38087_26350_37959_38100_38008_37881; BAIDUID_BFESS=563BF61659E928EFB7C9D1115AAAF5FF': 'FG=1; Hm_lvt_64ecd82404c51e03dc91cb9e8c025574=1675339841; Hm_lpvt_64ecd82404c51e03dc91cb9e8c025574=1675339848; APPGUIDE_10_0_2=1; REALTIME_TRANS_SWITCH=1; FANYI_WORD_SWITCH=1; HISTORY_SWITCH=1; SOUND_SPD_SWITCH=1; SOUND_PREFER_SWITCH=1; ab_sr=1.0.1_ODg2ZDZjMTY1ZmYzODkxYmQzZTY2OTdjY2VlMmZlNWIzZTMxMDExZjZkZTk0Mjc4M2U2NjQ4YTYyZGIwMjNmYzJmNzRkZDYzMDhkOWE2ZmZhZTUxYTMxZGNmZWFhNzA0ODdkNTljZDlkNWRjZDQ3MjE3NTY4ZTEzMjgzMGI3ZjY5ZTA2YzFmZWQ0ZTc2ZTNhMjYyOWE1NjIzMjlkZjVkMTViZDY2ODVhNzQzNzFiZDQ0YWFlNDg4NDc1MTNiYjNl",'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36',}data = {'from': 'en','to': 'zh','query': 'Python','transtype': 'enter','simple_means_flag': 3,'sign': 587387.791882,'token': '24a5c7161e43cff295e8c08eae4fa83f','domain': 'common',}data = urllib.parse.urlencode(data).encode('utf-8')request = urllib.request.Request(url=url, data=data, headers=headers)response = urllib.request.urlopen(request)content = response.read().decode('utf-8')print(content)
    ajax的get请求

    豆瓣电影持久化存储第一页数据

    import urllib.requestimport urllib.parseurl = 'https://movie.douban.com/j/chart/top_list?type=5&interval_id=100%3A90&action=&start=0&limit=20'# 获取豆瓣电影第一页的数据,并且保存headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36'}# 请求对象定制request = urllib.request.Request(url=url, headers=headers)# 获取响应数据response = urllib.request.urlopen(request)#获取数据进行持久化存储content=response.read().decode('utf-8')with open('../data/douban.json',mode='w',encoding='utf-8') as f:f.write(content)

    ajax的get请求之豆瓣电影持久化存储前十页数据

    import urllib.requestimport urllib.parseimport os# 获取豆瓣电影前十页的数据,并且保存def create_request(page):base_url = f'https://movie.douban.com/j/chart/top_list?type=5&interval_id=100%3A90&action='data = {'start': (page - 1) * 20,'limit': 20}data = urllib.parse.urlencode(data)url = base_url + dataheaders = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36'}# 请求对象定制request = urllib.request.Request(url=url, headers=headers)return requestdef get_content(request):resposne = urllib.request.urlopen(request,timeout=2)content = resposne.read().decode('utf-8')return contentdef download(page, content):with open('../data/douban/douban' + str(page) + '.json', 'w', encoding='utf-8') as f:print(f'正在下载第{str(page)}页,请耐心等待.....')f.write(content)if __name__ == '__main__':# 创建数据的保存目录if not os.path.exists('../data/douban'):os.mkdir('../data/douban')start_page = int(input('请输入起始页码:'))end_page = int(input('请输入结束的页码:'))for page in range(start_page, end_page + 1):# 每一页都有自己的请求对象的定制request = create_request(page)# 获取响应数据content = get_content(request)# 下载download(page, content)print(f'\n第{start_page}页到{end_page}下载完毕,请注意查看....')
    ajax的post请求
    import osimport urllib.requestimport urllib.parseimport urllib.errordef create_request(page):url = "http://www.kfc.com.cn/kfccda/ashx/GetStoreList.ashx?op=cname"data = {'cname': '北京','pid': '','pageIndex': page,'pageSize': 10}headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36'}data = urllib.parse.urlencode(data).encode('utf-8')request = urllib.request.Request(url=url, data=data, headers=headers)return requestdef get_content(request):try:resposne = urllib.request.urlopen(request,timeout=2)content = resposne.read().decode('utf-8')return contentexcept urllib.error.URLError as e:print(e)def download(content, page):with open('../data/kfc/kfc_' + str(page) + '.json', mode='w', encoding='utf-8') as f:print('正在保存第%s页数据...'.format(str(page)))f.write(content)if __name__ == '__main__':if not os.path.exists('../data/kfc'):os.mkdir('../data/kfc')start_page = int(input('请输入起始页码:'))end_page = int(input('请输入结束页码:'))for page in (start_page, end_page + 1):# 请求对象定制request = create_request(page)# 获取网页数据content = get_content(request)# 下载download(content, page)print('\n\n保存结束')
    URLError/HTTPError
    1.HTTPErroe类是URLError类的子类2.导入包urllib.error.HTTPError urllib.error.URLError3.http错误:http错误是针对浏览器无法连接到服务器而增加出来的错误提示,引导并告诉浏览器者该页是哪里出了问题4.通过urllib发送请求的时候,有可能会发送失败,可以通过try-execpt进行捕获异常,异常类有两个:URLError\HTTPError
    Handler处理器
    import urllib.requestimport urllib.parse# 使用Handler来访问百度,获取网页源码url='http://www.baidu.com'headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36'}request=urllib.request.Request(url=url,headers=headers)# handler build_opener open# 1. 获取handler对象handler=urllib.request.HTTPSHandler()# 2. 获取opener对象opener=urllib.request.build_opener(handler)# 3. 调用open方法response=opener.open(request)content=response.read().decode('utf-8')print(content)
    代理服务器

    快代理

    1.代理的常用功能?1.突破自身1P访问限,访问国外站点.2.访问一些单位或团体内部资通扩展:某大学年TP(前提是该代理地址在该资源的允许访问范园之内),使用教育网内地址设免费代理服务器,就可以用于对教育网开放的各类FTP下载上传,以及各类资料直间共享等服务。3.提高访问速度扩展:通常代理服务器都设置一个较大的硬白缓冲区,当有外界的信息通过时,同时也将其保存到暖冲区中,当其他用户再访问相同的信息时,则直接由领冲区中取出信息,传给用户,以提高访问速度。4.隐真实1P扩展:上网者也可以通过这种方法隐藏自己的P,免受攻击。2.代码配置代理创建Reugest对象创建ProxyHandleri对象用handler对象创建opener对家使用opener,open函数发送请求 import urllib.requesturl='https://www.baidu.com/s?ie=UTF-8&wd=ip'headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36'}request=urllib.request.Request(url=url,headers=headers)proxies={'http': '118.24.219.151:16817'}#模拟浏览器访问服务器handler=urllib.request.ProxyHandler(proxies=proxies)opener=urllib.request.build_opener(handler)resposne=opener.open(request)#获取相应信息content=resposne.read().decode('utf-8')with open('../data/代理.html','w',encoding='utf-8') as f:f.write(content)

    代理池

    import urllib.requestproxies_pool=[{'http':'118.24.219.151:16817'},{'http':'112.14.47.6:52024'},{'http':'222.74.73.202:42055'},{'http':'114.233.70.231:9000'},{'http':'116.9.163.205:58080'},{'http':'27.42.168.46:55481'},{'http':'121.13.252.61:41564'},{'http':'61.216.156.222:60808'},]import randomproxies=random.choice(proxies_pool)url='https://www.baidu.com/s?ie=UTF-8&wd=ip'headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36'}request=urllib.request.Request(url=url,headers=headers)handler=urllib.request.ProxyHandler(proxies=proxies)opener=urllib.request.build_opener(handler)response=opener.open(request)content=response.read().decode('utf-8')with open('../data/代理.html','w',encoding='utf-8') as f:f.write(content)

    2.1、requests模块

    pip install requests response的属性以及类型类型 :models.Responser.text:获取网站源码r.encoding:访问或定制编码方式r.url:获取请求的urIr.content:响应的字节类型r.status_code:响应的状态码r.headers:响应的头信息

    第一个爬虫程序

    需求:爬取搜狗首页页面数据

    # 导包import requsets# 指定urlurl='https://www.sogou.com/'# 发起请求#get方法会返回一个响应对象response=requests.get(url=url)# 获取响应数据:调用响应对象的text属性, 返回响应对象中存储的是字符串形式的响应数据(页面源码数据)page_text=response.textprint(page_text)# 持久化存储with open("sougou.html",'w',encoding='utf-8') as f: f.write(page_text)print('爬取结束')

    requests模块实战

    • text 返回字符串
    • content 返回二进制
    • json() 返回对象

    案例一:爬取搜狗指定词条对应的结果(简易网页采集器)

    import requestsget_url='https://www.sogou.com/web?'#处理url携带的参数,存到字典中kw=input("enter a message:")param={"query":kw}#UA伪装 :让爬虫对应的请求载体身份标识 伪装成某一款浏览器#UA:User-Agent 门户网站的服务器会检测对应请求的载体身份标识,如果检测到请求载体的身份标识是一款浏览器,,说明是一个正常的请求,但是检测到请求载体不是某一款浏览器,则表示该请求不正常(crawl),服务器端拒绝请求headers={"User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36"}response=requests.get(url=get_url,params=param,headers=headers)page_text=response.text# print(page_text)FielName=kw+'.html'with open(FielName,'w',encoding='utf-8') as f:f.write(page_text)print(kw +' save over!!')

    案例二:百度翻译

    #拿到当前单词所对应的翻译结果#页面局部刷新 ajaximport jsonimport requestspost_url='https://fanyi.baidu.com/sug'headers={"User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36"}word=input('请输入要翻译的单词:enter a word!!\n')#post请求参数处理data={"kw":word}response=requests.post(url=post_url,data=data,headers=headers)# print(response.text)dic_obj=response.json()#json返回的是字典对象,确认响应数据是json类型,才可使用json()with open(word+'.json','w',encoding='utf-8') as f:json.dump(dic_obj,fp=f,ensure_ascii=False)print('over')

    案例三:豆瓣电影

    #页面局部刷新,发起ajax请求import jsonimport requestsget_url='https://movie.douban.com/j/chart/top_list'headers={"User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36"}param={'type':'24','interval_id' : '100:90','action': '','start': '0',#从库中第几部电影去爬取'limit': '20', #一次取出多少个}respons=requests.get(url=get_url,params=param,headers=headers)list_data=respons.json()fp=open('./douban.json','w',encoding='utf-8')json.dump(list_data,fp,ensure_ascii=False)fp.close()print('over')

    案例四:肯德基官网餐厅查询,爬取多页餐厅信息

    动态加载,局部刷新,import jsonimport requestspost_url = 'http://www.kfc.com.cn/kfccda/ashx/GetStoreList.ashx?op=keyword'headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36"}for i in range(1,11):data = {'cname': '','pid': '','keyword': '北京','pageIndex': i,'pageSize': '10',}response requests.post(url=post_url,headers=headers, data=data)dic_data = response.textwith open('KFC_order', 'a', encoding='utf-8') as f:f.write(dic_data)print('\n') #爬取肯德基餐厅第一页数据import requests,ospost_url='http://www.kfc.com.cn/kfccda/ashx/GetStoreList.ashx?op=keyword'headers={"User-Agent":"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36"}mes= input('请输入一个城市:')data={'cname':'' ,'pid':'','keyword': mes,'pageIndex': '1','pageSize': '10',}#发送请求response=requests.post(url=post_url,headers=headers,data=data)#获取响应result=response.textif not os.path.exists('./网络爬虫/sucai/KFC/'):os.makedirs('./网络爬虫/sucai/KFC/')#持久化存储with open('./网络爬虫/sucai/KFC/'+mes+'KFC','w',encoding='utf-8') as f:f.write(result)

    (三)数据解析

    3.0概述

    回顾requests模块实现爬虫的步骤:

  • 指定url
  • 发情请求
  • 获取响应数据
  • 持久化存储
  • 起始在持久化存储之前还有一步数据解析,需要使用聚焦爬虫,爬取网页中部分数据,而不是整个页面的数据。下面会学习到三种数据解析的方式。至此,爬虫流程可以修改为:

  • 指定url
  • 发起请求
  • 获取响应
  • 数据解析
  • 持久化存储
  • 数据解析分类:

    • 正则
    • bs4
    • xpath

    数据解析原理概述:

    • 解析的局部的文本内容都会在标签之间或者标签对应的属性中进行存储
    • 进行指定标签的定位
    • 标签或者标签对应的属性中存储的数据值进行提取(解析

    3.1正则表达式

    # 元字符:具有固定含义的特殊符号#常用元字符'''. 匹配除换行符以外的任意字符\w 匹配字母或数字或下划线\s 匹配任意的空白符\d 匹配数字\n 匹配一个换行符\t 匹配一个制表符^ 匹配字符串的开始$ 匹配字符串结尾\W 匹配非字母或数字或下划线\S 匹配非空白符\D 匹配非数字a|b 匹配字符a 或者字符b() 匹配括号内的表达式,也表示一个组[.....] 匹配字符组中的字符[^......] 匹配除了字符串中的字符的所有字符'''#量词:控制前面的元字符出现的次数'''* 重复零次或更多次+ 重复一次或者更多次? 重复零次或一次{n} 重复n次{n,} 重复n次或者更多次{n,m} 重复n到m次'''#贪婪匹配和惰性匹配'''.* 贪婪匹配.*? 惰性匹配'''

    3.2re模块

    import re#findall:匹配字符串中所有的符合正则的内容【返回的是列表】list =re.findall(r'\d+',"我的电话号码是:10086,我女朋友的电话是:10010")print(list)# #finditer: 匹配字符串中所有的内容【返回的是迭代器】,从迭代器中拿到内容需要.group()it =re.finditer(r'\d+',"我的电话号码是:10086,我女朋友的电话是:10010")for item in it:print(item)print(item.group())# search 找到一个结果就返回,返回的结果是match对象,拿数据需要.group(), 【全文匹配】s= re.search(r'\d+',"我的电话号码是:10086,我女朋友的电话是:10010")print(s.group())#match是从头匹配s=re.match(r'\d+',"10086,我女朋友的电话是:10010")print(s.group())#预加载正则表达式s='''<p class='jay'><span id='1'>郭麒麟</span></p><p class='jj'><span id='2'>白鹿</span></p><p class='salry'><span id='3'>李一桐</span></p>'''#(?P<分组名字>正则) 可以单独从正则匹配的内容中进一步提取到内容obj=re.compile("<p class='(?P<daihao>.*?)'><span id='(?P<number>.*?)'>(?P<name>.*?)</span></p>")result=obj.finditer(s)for item in result:print(item.group("daihao"))print(item.group("number"))print(item.group("name"))

    案例一:豆瓣排行

    import csvimport requestsimport reurl ="https://movie.douban.com/top250"header={"User-Agent":"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36"}# 获取响应res=requests.get(url=url,headers=header)content_page=res.text# print(content_page)#解析数据obj=re.compile(r'<li>.*?<span class="title">(?P<name>.*?)</span>.*?'r'<p class="">.*?<br>(?P<time>.*?)&nbsp.*?<p class="star">.*?'r'<span class="rating_num".*?>(?P<grade>.*?)</span>.*?'r'<span>(?P<number>.*?)</span>',re.S)#开始匹配result=obj.finditer(content_page)f=open("data.csv",mode="w",encoding='utf-8')cswriter=csv.writer(f)for item in result:# print(item.group("name"))# print(item.group("time").strip())# print(item.group("grade"))# print(item.group("number"))dic=item.groupdict()dic['time']=dic['time'].strip()cswriter.writerow(dic.values())f.close()print("over")

    3.3 bs4数据解析

    数据解析原理

    1. 标签定位,2. 提取标签,标签属性中存储的数据值实例化一个beautifulSoup对象,并且将页面源码数据加载到该对象 中3. 通过调用BeautifulSoup对象中相关属性或者方法进行标签定位和数据提取

    环境安装:

    pip install bs4pip install lxml

    如何实例化BeautifulSoup对象

    • from bs4 import BeautifulSoup
    • 对象实例化:

    • 将本地的html文档中的数据加载到该对象中
    • fp=open('../01_初识爬虫/周杰伦.html','r',encoding='utf-8')soup=BeautifulSoup(fp,'lxml')print(soup)
    • 将互联网上获取的页面源码加载到该对象中
    • page_text=response.textsoup=BeautifulSoup(page_text,'lxml')print(soup)
    * 提供的用于数据解析的方法和属性:1. soup.tageName:返回的是文档中第一次出现的tageName对应的标签内容2. soup.find():* find('tageName'):等同于soup.p* 属性定位:- soup.find('p',class_='song')3. soup.find_all('a')#返回符合要求的所有的标签,返回列表soup.find_all(['a','span']) #获取多个标签,需要放在列表中.soup.find_all('li',limit=2) #查找前两个数据4. soup.select('.tang') #class_='tang' 返回复数,列表里* soup.select('某种选择器') 选择器可以是id,class ,标签选择器soup.select('.c1') soup.select('#l1')soup.select('li[id]') # 查找li标签有id的标签soup.select('li[id="l2"]') #查找li标签中id为l2的标签* 层级选择器使用:* soup.select('.tang > ul > li >a')[0] li > a # 子代选择器 用于选择某个元素下所有的一级子元素* soup.select('.tang > ul > li a')[0]li a # 后代选择器 用来选择某个元素的所有的后代元素* soup.select('li ~ a') #兄弟选择器 用于选取某个(或某些)元素的同级目录下所有的后面的标记* soup.select('p + a') # 相邻兄弟选择器 用于选取某个(或某些)元素同级目录下的下一个元素* soup.select('a,li') #标签a和标签li所有的数据5. 获取标签中的文本数据- soup.a.text /string/get_text()> text 和 get_text():可以获取一个某标签中所有的文本内容string只可以获取该标签下面直系的文本内容6. 获取标签中的属性值- soup.a['href']- soup.a.get('href')7. 获取标签的属性和属性值- soup.a.attrs 8. 获取标签的名字- ob=soup.select('#p1')[0].name- print(ob)

    案例一:爬取三国演义小说所有章节标题和章节内容

    三国演义

    import requestsfrom bs4 import BeautifulSoupfrom fake_useragent import UserAgentif __name__ =='__main__':headers={"User-Agent":UserAgent().chrome}get_url='https://www.shicimingju.com/book/sanguoyanyi.html'#发起请求,获取响应page_text=requests.get(url=get_url,headers=headers).text.encode('ISO-8859-1')#在首页中解析出章节标题和章节内容#1. 实例化BeautifulSoup对象,将html数据加载到该对象中soup=BeautifulSoup(page_text,'lxml')# print(soup)#2.解析章节标题和详情页的urllist_data=soup.select('.book-mulu > ul > li')fp=open('./sanguo.text','w',encoding='utf-8')for i in list_data:title=i.a.textdetail_url='https://www.shicimingju.com/'+ i.a['href']#对详情页的url发送请求,detail_text=requests.get(url=detail_url,headers=headers).text.encode('ISO-8859-1')detail_soup=BeautifulSoup(detail_text,'lxml')#获取章节内容content=detail_soup.find('p',class_='chapter_content').text#持久化存储fp.write(title+":"+content+"\n")print(title,'下载完成')

    **案例二:**星巴克菜单名

    from bs4 import BeautifulSoupimport urllib.requesturl = 'https://www.starbucks.com.cn/menu/'headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36'}request = urllib.request.Request(url=url, headers=headers)response = urllib.request.urlopen(request)content = response.read().decode('utf-8')soup = BeautifulSoup(content, 'lxml')# //ul[@class="grid padded-3 product"]//strong/text()name_list=soup.select('ul[class="grid padded-3 product"] strong')for name in name_list:print(name.get_text()) import requestsfrom bs4 import BeautifulSoupimport timeheaders = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36'}def get(url):response = requests.get(url, headers=headers)if response.status_code == 200:response.encoding = 'utf-8'content = response.textparse(content)def parse(html):soup = BeautifulSoup(html, 'lxml')# //ul[@class="grid padded-3 product"]/li/a/strong/text()name_list = soup.select('ul[class="grid padded-3 product"] strong')lists = []for name in name_list:lists.append(name.get_text())itemipiline(lists)import csvdef itemipiline(name):for i in name:with open('星巴克.csv', 'a', encoding='utf-8') as fp:writer = csv.writer(fp)writer.writerows([i])if __name__ == '__main__':url = 'https://www.starbucks.com.cn/menu/'get(url)

    3.4xpath数据解析

    1. 安装lxml库pip install lxml2. 导入 lxml.etreefrom lxml import etree3. etree.parse() 解析本地文件html_tree=etree.parse('xx.html')4. etree.HTML() 解析服务器响应文件html_tree=etree.HTML(response.read().decode("utf-8"))5. html_tree.xpath(xpath路径)
    • 实例化etree对象,且将被解析的源码加载到该对象中
    • 调用etree对象中的xpath方法,结合着xpath表达式实现标签定位和内容的捕获
    xpath基本语法:1.路径查询//:查找所有子孙节点,不考虑层级关系/:找直接子节点2.谓词查询//p[@id]//p[@id="maincontent"]3.属性查询//@class4.模糊查询//p[contains(@id,"he")]//p[starts-with(@id,"he")]5.内容查询//p/h1/text()6.逻辑运算//p[@id="head"and @class="s_down"]//title | //price <body><ul><li id="l1" class="c1">北京</li><li id="l2">上海</li><li id="c3">山东</li><li id="c4">四川</li></ul></body> from lxml import etree# xpath解析本地文件tree = etree.parse('../../data/xpath的基本使用.html')# 查找url下的li# li_list=tree.xpath('//body/ul/li')# 查找所有id的属性的li标签# text()获取标签中的内容# li_list = tree.xpath('//ul/li[@id]/text()')# 找到id为l1的li标签,注意引号问题# li_list=tree.xpath('//ul/li[@id="l1"]/text()')# 查找到id为l1的li标签的class属性值# li_list=tree.xpath('//ul/li[@id="l1"]/@class')# 查找id中包含l的li标签# li_list=tree.xpath('//ul/li[contains(@id ,"l")]/text()')# 查找id的值以c开头的li标签# li_list = tree.xpath('//ul/li[starts-with(@id ,"c")]/text()')#查询id为li和class为c1的# li_list = tree.xpath('//ul/li[@id="l1" and @class="c1"]/text()')#查询li_list = tree.xpath('//ul/li[@id="l1"]/text() | //ul/li[@id="l2"]/text()')# 判断列表的长度print(len(li_list))print(li_list)

    **案例零:**获取百度网站的百度一下

    from lxml import etreeimport urllib.request# 1.获取网页源码# 2.解析服务器响应的文件 etree.HTML# 3.打印url = 'https://www.baidu.com/'headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36'}# 请求对象定制request = urllib.request.Request(url=url, headers=headers)# 模拟浏览器向服务器获取响应response = urllib.request.urlopen(request)# 获取网页源码content = response.read().decode('utf-8')# 解析网页源码,获取数据tree = etree.HTML(content)#xpath返回值是一个列表res = tree.xpath('//input[@id="su"]/@value')[0]print(res)

    案例一:彼岸图网

    import requestsfrom lxml import etreeimport osfrom fake_useragent import UserAgentheaders={"User-Agent":UserAgent().chrome}url='https://pic.netbian.com/4kdongman/'#发请求,获取响应response=requests.get(url=url,headers=headers)page_text=response.text#数据解析 src属性 alt属性tree=etree.HTML(page_text)list_data=tree.xpath('//p[@class="slist"]/ul/li')#print(list_data)#创建文件夹if not os.path.exists('./PicLibs'):os.mkdir('./PicLibs')for li in list_data:img_src='https://pic.netbian.com' + li.xpath('./a/img/@src')[0]img_name=li.xpath('./a/img/@alt')[0]+'.jpg'#print(img_name,img_src)#通用解决中文乱码的解决方案img_name=img_name.encode("iso-8859-1").decode('gbk')#请求图片,进行持久化存储img_data=requests.get(url=img_src,headers=headers).contentimg_path='PicLibs/'+ img_namewith open(img_path,'wb') as fp:fp.write(img_data)print(img_name,'下载成功!!!!')

    案例二:发表情

    import requests,osfrom lxml import etreefrom fake_useragent import UserAgentdef crawl(url):headers={"User-Agent":UserAgent().chrome}#获取页面响应信息page_text=requests.get(url,headers).text#解析表情包的详情页urltree=etree.HTML(page_text)list_data=tree.xpath('//p[@class="ui segment imghover"]/p/a')if not os.path.exists('表情包'):os.mkdir('表情包')for i in list_data:detail_url='https://www.fabiaoqing.com'+i.xpath('./@href')[0]# print(detail_url)#对详情页面url发起请求,获取响应detail_page_text=requests.get(detail_url,headers).texttree=etree.HTML(detail_page_text)#得到搞笑图片的地址,发起请求进行持久化存储detail_list_data=tree.xpath('//p[@class="swiper-wrapper"]/p/img/@src')[0]fp=detail_list_data.split('/')[-1]with open('表情包/'+fp, 'wb') as fp:fp.write(requests.get(detail_list_data).content)print(fp,'下载完了!!!')#调用crawl('https://www.fabiaoqing.com/biaoqing/lists/page/1.html')

    案例三:绝对领域

    import requestsfrom fake_useragent import UserAgentfrom lxml import etreeimport osdef crawl():url='https://www.jdlingyu.com/tuji'headers={"User-Agent":UserAgent().chrome}#获取页面代码page_text=requests.get(url=url,headers=headers).text#print(page_text)tree=etree.HTML(page_text)list_url=tree.xpath('//li[@class="post-list-item item-post-style-1"]/p/p/a')#print(list_url)for i in list_url:detail_link=i.xpath('./@href')[0]#print(detail_link)#对详情页url发请求,解析详情页图片detail_data=requests.get(detail_link,headers).texttree=etree.HTML(detail_data)#名称list_name=tree.xpath('//article[@class="single-article b2-radius box"]//h1/text()')[0]# 创建一个相册文件夹文件夹if not os.path.exists('绝对领域\\'+list_name):os.makedirs('绝对领域\\'+list_name)#图片链接list_link=tree.xpath('//p[@class="entry-content"]/p')for i in list_link:img_src=i.xpath('./img/@src')[0]# 图片名称pic_name=img_src.split('/')[-1]with open(f'绝对领域\\{list_name}\\{pic_name}','wb') as fp:fp.write(requests.get(img_src).content)print(list_name,'下载完成')crawl()

    案例四:爬取站长素材中免费简历模板

    # 爬取站长素材中免费简历模板 官网:https://sc.chinaz.com/from lxml import etreeimport requests,osimport time if __name__ == '__main__':url = 'https://sc.chinaz.com/jianli/free.html'headers = {"user-agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36"}# 获取免费简历模板网页数据page_text = requests.get(url=url, headers=headers).text#print(page_text)#解析数据,获取页面模板对应详情页面的urltree=etree.HTML(page_text)list_data= tree.xpath('//p[@id="main"]/p/p')#print(list_data)if not os.path.exists('./网络爬虫/sucai/站长素材'):os.makedirs('./网络爬虫/sucai/站长素材')for i in list_data:#解析出先要的详情页面链接detail_url ='https:'+i.xpath('./a/@href')[0]#获取简历模板名字detail_name =i.xpath('./p/a/text()')[0]detail_name=detail_name.encode('iso-8859-1').decode('utf-8')#解决中文编码问题#print(detail_name)#print(detail_url)#对详情页url发送请求,进行下载detail_data_text=requests.get(url=detail_url,headers=headers).text#进行数据解析,得到下载地址tree=etree.HTML(detail_data_text)down_link= tree.xpath('//p[@class="clearfix mt20 downlist"]/ul/li[1]/a/@href')[0]#print(down_link)#最后对下载地址链接发送请求,获取二进制数据final_data= requests.get(url=down_link,headers=headers).content#print(final_data)file_path='./网络爬虫/sucai/站长素材/'+detail_namewith open(file_path,'wb') as fp:fp.write(final_data)time.sleep(1) #下载延时1秒,防止爬取太快print(detail_name+'下载成功!!!')

    **案例五:**站长素材高清风景图片

    import urllib.requestfrom lxml import etreeimport osdef create_request(page):if page == 1:url = 'https://sc.chinaz.com/tupian/fengjingtupian.html'else:url = f'https://sc.chinaz.com/tupian/fengjingtupian_{str(page)}.html'headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36'}request = urllib.request.Request(url=url, headers=headers)return requestdef get_content(request):response = urllib.request.urlopen(request)content = response.read().decode('utf-8')return contentdef download(content):# 下载图片#'D:\图片\风景'tree=etree.HTML(content)src_list=tree.xpath('//body/p[3]/p[2]/p/img/@data-original')name_list=tree.xpath('//body/p[3]/p[2]/p/img/@alt')# urllib.request.urlretrieve('','D:\图片\风景')for i in range(len(name_list)):name=name_list[i]url='https:'+src_list[i]print('正在下载 %s [%s]' % (name, url))urllib.request.urlretrieve(url=url,filename=fr'D:\图片\风景\{name}.jpg')if __name__ == '__main__':if not os.path.exists('D:\图片\风景'):os.mkdir('D:\图片\风景')start_page = int(input('请输入起始页码:'))end_page = int(input('请输入结束页码:'))for page in range(start_page, end_page + 1):# 1.请求对象的定制request = create_request(page)# 2.获取网页源码content = get_content(request)# 3.下载download(content)

    3.5jsonpaht数据解析

    安装pip install jsonpath使用obj=json.load(open('json文件','r',encoding="utf-8"))ret=jsonpath.jsonpath(obj,'jsonpah语法')

    博客教程

    json格式数据

    { "store": {"book": [{ "category": "reference","author": "Nigel Rees","title": "Sayings of the Century","price": 8.95},{ "category": "fiction","author": "Evelyn Waugh","title": "Sword of Honour","price": 12.99},{ "category": "fiction","author": "Herman Melville","title": "Moby Dick","isbn": "0-553-21311-3","price": 8.99},{ "category": "fiction","author": "J. R. R. Tolkien","title": "The Lord of the Rings","isbn": "0-395-19395-8","price": 22.99}],"bicycle": {"author":"老王","color": "red","price": 19.95}}}

    提取数据

    import jsonpathimport jsonobj = json.load(open('../../../data/jsonpath.json', 'r', encoding='utf-8'))# 书店所有的书的作者# author_list=jsonpath.jsonpath(obj,'$.store.book[*].author')# 所有的作者# author_list =jsonpath.jsonpath(obj,'$..author')# store的所有元素。所有的bookst和bicycle# tag_list=jsonpath.jsonpath(obj,'$.store.*')# store里面所有东西的price# tag_list = jsonpath.jsonpath(obj, '$.store..price')# 第三个书# book=jsonpath.jsonpath(obj,'$..book[2]')# 最后一本书# book=jsonpath.jsonpath(obj,'$..book[(@.length-1)]')# 前面的两本书。# book=jsonpath.jsonpath(obj,'$..book[0,1]')# book=jsonpath.jsonpath(obj,'$..book[:2]')# 过滤出所有的包含isbn的书。# book = jsonpath.jsonpath(obj, '$..book[?(@.isbn)]')#过滤出价格高于10的书。book=jsonpath.jsonpath(obj,'$..book[?(@.price>10)]')print(book)

    (四)爬虫进阶

    4.0验证码识别

    • 验证码是门户网站中采取的一种反扒机制

    • 反扒机制:验证码,识别验证码图片中的数据,用于模拟登陆操作

    4.1 cookie

    • http/https协议特性:无状态

    • 没有请求到对应页面数据的原因:发起第二次基于个人主页请求的时候,服务器端并不知道该此请求是基于登陆状态下的请求

    • cookie:

    用来让服务器端记录客户端的相关信息

  • 手动处理:通过抓包工具获取cookie值,将该值封装到headers中 (不建议)
  • 自动处理: 进行模拟登陆post请求后,由服务器创建,
    • session会话对象
      • 作用:1. 可以进行请求发送
        2.如果请求过程中产生了cookie,则该cookie会被自动存储/携带在该session对象中
    • 使用:
      1. 创建session对象
      2. 使用session对象进行模拟登陆post请求的发送,(cookie就会被存储在session中)
      3. session对象对个人主页对应的get请求进行发送(携带了cookie)
    #17k小说网import requests#会话session=requests.session()data={'loginName': '自己的账户名','passwod': '自己的密码',}#1.登陆url='https://passport.17k.com/ck/user/login'rs=session.post(url=url,data=data)#print(response.text)#print(response.cookies)#看cookie#2.拿数据#使用携带cookie的session进行get请求发送response=session.get('https://user.17k.com/ck/author/shelf?page=1&appKey=2406394919')print(response.json())

    另一种携带cookie访问

    直接把cookie放在headers中(不建议)

    #另一种携带cookie访问res=requests.get('https://user.17k.com/ck/author/shelf?page=1&appKey=2406394919',headers={"Cookie":"GUID=868e19f9-5bb3-4a1e-b416-94e1d2713f04; BAIDU_SSP_lcr=https://www.baidu.com/link?url=r1vJtpZZQR2eRMiyq3NsP6WYUA45n6RSDk9IQMZ-lDT2fAmv28pizBTds9tE2dGm&wd=&eqid=f689d9020000de600000000462ce7cd7; Hm_lvt_9793f42b498361373512340937deb2a0=1657699549; sajssdk_2015_cross_new_user=1; c_channel=0; c_csc=web; accessToken=avatarUrl%3Dhttps%253A%252F%252Fcdn.static.17k.com%252Fuser%252Favatar%252F08%252F08%252F86%252F97328608.jpg-88x88%253Fv%253D1657699654000%26id%3D97328608%26nickname%3D%25E9%2585%25B8%25E8%25BE%25A3%25E9%25B8%25A1%25E5%259D%2597%26e%3D1673251686%26s%3Dba90dcad84b8da40; sensorsdata2015jssdkcross=%7B%22distinct_id%22%3A%2297328608%22%2C%22%24device_id%22%3A%22181f697be441a0-087c370fff0f44-521e311e-1764000-181f697be458c0%22%2C%22props%22%3A%7B%22%24latest_traffic_source_type%22%3A%22%E7%9B%B4%E6%8E%A5%E6%B5%81%E9%87%8F%22%2C%22%24latest_referrer%22%3A%22%22%2C%22%24latest_referrer_host%22%3A%22%22%2C%22%24latest_search_keyword%22%3A%22%E6%9C%AA%E5%8F%96%E5%88%B0%E5%80%BC_%E7%9B%B4%E6%8E%A5%E6%89%93%E5%BC%80%22%7D%2C%22first_id%22%3A%22868e19f9-5bb3-4a1e-b416-94e1d2713f04%22%7D; Hm_lpvt_9793f42b498361373512340937deb2a0=1657700275"})print(res.json())

    cookie登录古诗文网

    import requestsfrom bs4 import BeautifulSoupimport urllib.request# https://so.gushiwen.cn/user/login.aspx?from=http%3a%2f%2fso.gushiwen.cn%2fuser%2fcollect.aspxheaders = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36'}login_url = 'https://so.gushiwen.cn/user/login.aspx?from=http://so.gushiwen.cn/user/collect.aspx'response = requests.get(url=login_url, headers=headers)content = response.text# 解析页面源码,分别获取 __VIEWSTATE __VIEWSTATEGENERATORsoup = BeautifulSoup(content, 'lxml')viewstate = soup.select('#__VIEWSTATE')[0].attrs.get('value')viewstategenerator = soup.select('#__VIEWSTATEGENERATOR')[0].attrs.get('value')# 获取验证码图片并保存img = soup.select('#imgCode')[0].attrs.get('src')code_url = 'https://so.gushiwen.cn/' + img# requests里有一个方法 session(),通过session的返回值,就能使请求变成一个对象# urllib.request.urlretrieve(code_url, '../../../data/code.jpg')session = requests.session()response_code = session.get(code_url)# 图片下载需要使用二进制下载content_code = response_code.contentwith open('../../../data/code.jpg', 'wb') as f:f.write(content_code)code_name = input('请输入验证码:')# 点击登录接口url_post = 'https://so.gushiwen.cn/user/login.aspx?from=http%3a%2f%2fso.gushiwen.cn%2fuser%2fcollect.aspx'data = {'__VIEWSTATE': viewstate,'__VIEWSTATEGENERATOR': viewstategenerator,'from': 'http://so.gushiwen.cn/user/collect.aspx','email': 'xxxxxxxxx','pwd': '123123','code': code_name,'denglu': '登录',}response_post = session.post(url=url_post, headers=headers, data=data)content_post = response_post.textwith open('../../../data/古诗文.html', 'w', encoding='utf-8') as f:f.write(content_post)

    4.2代理

    破解封IP的各种反扒机制

    • 社么是代理:
      1. 代理服务器
    • 代理作用:
      * 突破自身IP访问的限制
      * 可以隐藏自身真实IP
    • 代理相关网站:
      • 快代理
        • www.goubanjia.com
          • 西祠代理
    • 代理IP类型:
      * http:应用到http协议对应的URL中
      * https:应用到https协议对应的URL中
    • 代理IP的匿名度:
      • 透明:服务器知道该次请求使用了代理, 也知道请求对应的真实IP
      • 匿名:知道使用代理,不知道真实IP
      • 高匿名:不知道使用代理,更不知道真实IP

    案例

    requests

    import requestsproxies={#"http":"""https":"222.110.147.50:3218" #代理IP地址}res=requests.get("https://www.baidu.com/s?tn=87135040_1_oem_dg&ie=utf-8&wd=ip",proxies=proxies)res.encoding='utf-8'with open('ip.html','w') as fp:fp.write(res.text)

    urllib.request

    import urllib.requesturl='https://www.baidu.com/s?ie=UTF-8&wd=ip'headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36'}request=urllib.request.Request(url=url,headers=headers)proxies={'http': '118.24.219.151:16817'}#模拟浏览器访问服务器handler=urllib.request.ProxyHandler(proxies=proxies)opener=urllib.request.build_opener(handler)resposne=opener.open(request)#获取相应信息content=resposne.read().decode('utf-8')with open('../data/代理.html','w',encoding='utf-8') as f:f.write(content)

    4.3防盗链

    在访问一些链接的时,网站会进行溯源

    他所呈现的反扒核心:当网站中的一些地址被访问的时候,会溯源到你的上一个链接
    需要具备的一些“环境因素”,例如访问的过程中需要请求的时候携带headers

    案例:梨视频

    #分析#请求返回的地址 和 可直接观看的视频的地址有差异##请求返回的数据中拼接了返回的systemTime值#真实可看的视频地址中有浏览器地址栏的ID值#解决办法,将url_no中的systemTime值替换为cont-拼接的ID值#url='https://www.pearvideo.com/video_1767372import requests,osif not os.path.exists('./网络爬虫/sucai/梨video'):os.makedirs('./网络爬虫/sucai/梨video')#梨视频爬取视频url='https://www.pearvideo.com/video_1767372'contId= url.split('_')[1]headers={"User-Agent":"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36","Referer":url,}videoStatusUrl=f'https://www.pearvideo.com/videoStatus.jsp?contId={contId}&mrd=0.6004271686556242'res =requests.get(videoStatusUrl,headers=headers)#print(res.json())dic=res.json()srcUrl=dic['videoInfo']['videos']['srcUrl']systemTime=dic['systemTime']srcUrl=srcUrl.replace(systemTime,f'cont-{contId}')#下载视频,进行存储video_data=requests.get(url=srcUrl,headers=headers).contentwith open('./网络爬虫/sucai/梨video/video.mp4','wb')as fp:fp.write(video_data)

    (五)异步爬虫

    目的:在爬虫中使用异步实现高性能的数据爬取操作

    • 进程是资源单位,每一个进程至少要有一个线程
    • 线程是执行单位
    • 同一个进程中的线程可共享该进程的资源
    • 启动每一个城西默认都会有一个主线程

    异步爬虫方式:

  • 多线程,多进程:(不建议)

    • 好处:可以被相关阻塞的操作单独开启线程或者进程,阻塞操作就可以异步执行
    • 弊端:无法无限开启多线程或者多进程。
  • 进程池,线程池(适当使用)

    • 好处:可以降低系统对进程或者线程创建和销毁一个频率,从而很好的降低系统的开销
    • 弊端:池中线程或者进程的数量有上限
    • 线程池
  • 一次性开辟一些线程,我们用户直接给线程池提交任务,线程任务的调度交给线程池来完成

  • 单线程+异步协程
    • event_loop:事件循环,相当于一个无限循环,我们可以把一些函数注册到这个事件循环上
      当满足某些条件的时候,函数就会被循环执行
    • coroutine:协程对象,我们可以将协程对象注册到事件循环中,它会被事件循环调用。
      我们可以使用 async关键字来定义一个方法,这个方法在调用时不会立即执行,而是返回一个协程对象
    • task:任务,它是对协程对象的进一步封装,包含了任务的各个状态,
    • future:代表将来执行或还没有执行的任务,实际上和task没有本质区别
    • async:定义一个协程
    • await用来挂起阻塞方法的执行

    5.0单线程

    headers={"User-Agent":"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36"}urls=['https://www.51kim.com/''https://www.baidu.com/''https://www.baidu.com/']

    5.1多线程

    from threading import Thread #线程类def fun():for i in range(100):print('func',i)if __name__ =='__main__':t=Thread(target=fun) #创建线程,为线程安排任务t.start() #多线程状态为可以开始工作的状态,具体执行时间由CPU决定for i in range(100):print('main',i)'''def MyThread(Thread):def run(self): #当线程被执行的时候,被执行的就是runfor i in range(1000):print("子线程",i)if __name__ =='__main__':t=MyThread()t.start()#开启线程for i in range(1000):print("主线程",i)'''def fun(name):for i in range(1000):print(name,i)if __name__ =='__main__':#给进程起名字,分得清楚是哪个进程t1=Thread(target=fun,args=('周杰伦',)) #传递参数必须是元组t1.start() t2=Thread(target=fun,args=('周星驰',)) t2.start()

    5.2多进程

    from multiprocessing import Processdef fun():for i in range(10000):print('子进程',i)if __name__ =='__main__':p=Process(target=fun) p.start() for i in range(10000):print('主进程',i)

    案例:基于进程+线程实现多任务爬虫

    需求分析

    import uuidfrom multiprocessing import Queue, Processfrom threading import Threadimport pymysqlfrom lxml import etreeimport requestsheaders = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36'}class DownloadThread(Thread):def __init__(self, url):super().__init__()self.url = urldef run(self):print('开始下载', self.url)resp = requests.get(url=self.url, headers=headers)if resp.status_code == 200:resp.encoding = 'utf-8'self.content = resp.textprint(self.url, '下载完成')def get_content(self):return self.contentclass DownloadProcess(Process):"""下载进程"""def __init__(self, url_q, html_q):self.url_q: Queue = url_qself.html_q = html_qsuper().__init__()def run(self):while True:try:url = self.url_q.get(timeout=30)# 将下载任务交给子线程t = DownloadThread(url)t.start()t.join()# 获取下载的数据html = t.get_content()# 将数据压入到解析队列中self.html_q.put((url, html))except:breakprint('---下载进程over---')class ParseThread(Thread):def __init__(self, html, url_q):self.html = htmlself.url_q = url_qsuper().__init__()def run(self):tree = etree.HTML(self.html)imgs = tree.xpath('//p[contains(@class,"com-img-txt-list")]//img')for img in imgs:item = {}item['id'] = uuid.uuid4().hexitem['name'] = img.xpath('./@data-original')[0]item['src'] = img.xpath('./@alt')[0]# 将item数据写入数据库conn = pymysql.connect(user='root',password='123.com',host='127.0.0.1',port=3306,database='dog',charset='utf8')cursor = conn.cursor()sql = 'insert into labuladuo(name,src) values("{}","{}")'.format(item['name'], item['src'])cursor.execute(sql)conn.commit()cursor.close()conn.close()print(item)# 获取下一页的链接next_page = tree.xpath('//a[@class="nextpage"]/@href')if next_page:next_url = 'https://sc.chinaz.com/tupian/' + next_page[0]self.url_q.put(next_url) # 将新的下载任务放到下载队列中class ParseProcess(Process):"""解析进程"""def __init__(self, url_q, html_q):super().__init__()self.url_q = url_qself.html_q = html_qdef run(self):while True:try:# 读取解析的任务url, html = self.html_q.get(timeout=60)# 启动解析线程print('开始解析', url)t = ParseThread(html, self.url_q).start()except:breakprint('---解析进程over---')if __name__ == '__main__':task1 = Queue() # 下载任务队列task2 = Queue() # 解析任务队列# 起始爬虫任务task1.put('https://sc.chinaz.com/tupian/labuladuo.html')p1 = DownloadProcess(task1, task2)p2 = ParseProcess(task1, task2)p1.start()p2.start()p1.join()p2.join()print('Over !!!!')

    5.3线程池和进程池

    from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutordef fun(name):for i in range(1000):print(name,i)if __name__ =='__main__':#创建线程池with ThreadPoolExecutor(50) as t:for i in range(100):t.submit(fun,name=f'线程{i}')#等待线程池中的任务全部执行完毕,才继续执行print('123132')'''def fun(name):for i in range(1000):print(name,i)if __name__ =='__main__':#创建进程池with ProcessPoolExecutor(50) as t:for i in range(100):t.submit(fun,name=f'进程{i}')#等待线程池中的任务全部执行完毕,才继续执行print('123132')'''

    案例:水果交易网

    #https://www.guo68.com/sell?page=1中国水果交易市场#先考虑如何爬取单个页面的数据#再用线程池,多页面爬取import requests,csv,osfrom fake_useragent import UserAgentfrom concurrent.futures import ThreadPoolExecutorfrom lxml import etreeif not os.path.exists('./网络爬虫/sucai/中国水果交易'):os.makedirs('./网络爬虫/sucai/中国水果交易')f='./网络爬虫/sucai/中国水果交易/fruit.csv'fp=open(f,'w',encoding='utf-8')csvwrite=csv.writer(fp)def download_one_page(url):#拿去页面代码response=requests.get(url=url,headers={"user-agent":UserAgent().chrome}).text#解析数据 tree=etree.HTML(response)list_data=tree.xpath('//li[@class="fruit"]/a')all_list_data=[]for i in list_data:list_price=i.xpath('./p[2]/span[1]/text()')[0]list_sort=i.xpath('./p[1]/text()')[0]list_address=i.xpath('./p[2]/text()')[0]all_list_data.append([list_price,list_sort,list_address])#print(list_price,list_address,list_sort)#持久化存储csvwrite.writerow(all_list_data)print(url,'下载完毕')if __name__ =='__main__':with ThreadPoolExecutor(50) as Thread: #使用线程池,池子里放50个线程for i in range(1,60):Thread.submit(download_one_page,f'https://www.guo68.com/sell?page={i} ')print("全部下载完毕")

    5.4协程

    • 协程不是计算机真实存在的(计算机只有进程,线程),而是由程序员人为创造出来的。

    • 协程也可以被成为微线程,是一种用户态内的上下文切换技术,简而言之,其实就是通过一个线程实现代码块相互切换执行

    • 实现协程方法:

      • 通过greenlet,早期模块

      • yield关键字

      • asyncio模块

      • async,await关键字 【推荐】

    • 协程意义

    • 在一个线程中如果遇到IO等待时间,线程不会傻傻等,利用空闲的时间再去干点别的事

    import time def fun():print('*'*50)time.sleep(3)#让当前线程处于阻塞状态,CPU是不为我工作的print('*****'*10)#input() 程序也是处于阻塞状态# requests.get(bilibili)在网络请求返回数据之前,程序也是处于阻塞状态# 一般情况下,当程序处于 IO操作的时候,线程都会处于阻塞状态# 协程:当程序遇见IO操作的时候,可以选择性的切换到其他任务上# 在微观上是一个任务一个任务的切换进行,切换条件一般是IO操作# 宏观上,我们能看到的其实就是多个任务一起在执行# 多任务异步操作# 上方所讲都是在单线程条件下if __name__ =='__main__':fun() #Python 协程import asyncioimport time'''async def fun1():print("你好,我叫周星驰")time.sleep(3) #当程序出现同步操作的时候,异步就中断了,requests.getawait asyncio.sleep(3) #异步操作代码print('我叫周星驰')async def fun2():print("你好,我叫周杰伦")time.sleep(2)await asyncio.sleep(2)print('我叫周杰伦')async def fun3():print("你好,我叫周星驰")time.sleep(4)await asyncio.sleep(4)print('我叫小潘')if __name__ =="__main__":f1 =fun1()#此时的函数是异步协程函数,此时函数执行得到的是一个协程对象#print(g)#asyncio.run(g) #协程需要asyncio模块的支持f2=fun2()f3=fun3()task=[f1,f2,f3]t1=time.time()#一次性启动多个任务(协程)asyncio.run(asyncio.wait(task))t2=time.time()print(t2-t1)'''async def fun1():print("你好,我叫周星驰") await asyncio.sleep(3) #异步操作代码print('我叫周星驰')async def fun2():print("你好,我叫周杰伦")await asyncio.sleep(2)print('我叫周杰伦')async def fun3():print("你好,我叫周星驰")await asyncio.sleep(4)print('我叫小潘')async def main():#第一种写法#fi=fun1()#await f1 #一般await挂起操作放在协程对象前面#第二种写法task=[asyncio.create_task(fun1()), #py3.8版本以后加上asyncio.create_task()asyncio.create_task(fun2()),asyncio.create_task(fun3())]await asyncio.wait(task)if __name__ =="__main__":t1=time.time()asyncio.run(main())t2=time.time()print(t2-t1)
    5.4.1async & await关键字

    python3.4之后版本能用

    import asyncioasync def fun1():print(1)await asyncio.sleep(2)#遇到IO操作时,自动切换到task中的其他任务print(2)async def fun2():print(3)await asyncio.sleep(3)#遇到IO操作时,自动切换到task中的其他任务print(4)task=[asyncio.ensure_future(fun1())asyncio.ensure_future(fun2())]loop= asyncio.get_event_loop()loop.run_untile_complete(asyncio.wait(task))#遇到IO阻塞自动切换
    5.4.2事件循环

    理解为一个死循环,检测并执行某些代码

    import asyncio#生成或获取一个事件循环loop=asyncio.get_event_loop()#将任务放到‘任务列表’loop.run_untile_complete(任务)
    5.4.3快速上手

    协程函数:定义函数时,async def 函数名
    协程对象:执行 协程函数(),得到的就是协程对象

    async def func():passresult=func()

    执行协程函数创建协程对象,函数内部代码不会执行
    如果想要执行协程函数内部代码,必须要将协程对象交给事件循环来处理

    import asyncioasync def func():pritn("你好,中国)result=func()#loop=asyncio.get_event_loop()#loop.run_untile_complete(result)asyncio.run(result)
    5.4.4await关键字

    await +可等待对象(协程对象,task对象,Future对象=====》IO等待)

    示例1:

    import asyncioasync def fun():print("12374")await asyncio.sleep(2)print("结束")asyncio.run(fun())

    示例2:

    import asyncioasync def others():print('start')await asyncio.sleep(2)print('end")return '返回值'async def fun():print("执行协程函数内部代码")#遇到IO操作 挂起当前协程(任务),等IO操作完成之后再继续往下执行,当前协程挂起时,事件循环可以去执行其他些协程(任务)response =await others()print('IO请求结束,结果为:',response)asyncio.run(fun())

    示例3:

    import asyncioasync def others():print('start')await asyncio.sleep(2)print('end")return '返回值'async def fun():print("执行协程函数内部代码")#遇到IO操作 挂起当前协程(任务),等IO操作完成之后再继续往下执行,当前协程挂起时,事件循环可以去执行其他些协程(任务)response1 =await others()print('IO请求结束,结果为:',response1)response2 =await others()print('IO请求结束,结果为:',response2)asyncio.run(fun())

    await 等待对象的值得到结果之后才继续往下走

    5.4.5Task对象

    白话:在事件循环中添加多个任务
    Task用于并发调度协程,通asyncio.create_task(协程对象)的方式创建Task对象,这样可以让协程加入事件循环中等待被调度执行,除了使用asyncio.create_task()函数以外,还可以用低级的loop.create_task()或者ensure_future()函数。不建议手动实例化Task对象

    示例1:

    import asyncioasync def func():print(1)await asyncio.sleep(2)print(2)return '返回值'async def main():print("main开始")#创建Task对象,将当前执行func()函数任务添加到事件循环中task1=asyncio.create_task(func())#创建Task对象,将当前执行func()函数任务添加到事件循环中task2=asyncio.creat_task(func())print("main结束")#当执行某协程遇到IO操作时,会自动化切换执行其他任务#此处的await是等待相对应的协程全部都执行完毕并获取结果res1=await task1res2=await task2print(res1,res2)asyncio.run(main())

    示例2:

    import asyncioasync def func():print(1)await asyncio.sleep(2)print(2)return '返回值'async def main():print("main开始")task_list=[asyncio.create_task(func(),name="n1")asyncio.create_task(func(),name="n2")]print("main结束")#返回值会放到done里面,done,pending=await asyncio.wait(task_list,timeout=None)print(done)asyncio.run(main())

    示例3:

    import asyncioasync def func():print(1)await asyncio.sleep(2)print(2)return '返回值'task_list=[func(),func(),]done,pending=asyncio.run( asyncio.wait(task_list))print(done)
    5.4.6async.Future对象

    Task继承Tuture对象,Task对象内部await结果的处理基于Future对象来的
    示例1:

    async def main():#获取当前事件循环loop=asyancio.get_running_loop()#创建一个任务(Future对象)这个任务什么都不干fut=loop.create_future()#等待任务最终结果(Future对象),没有结果则会一直等下去await futasyncio.run(main())

    示例2:

    async def set_after():await asyncio.sleep(2)fut.set_result("666")async def main():#获取当前事件循环loop=asyancio.get_running_loop()#创建一个任务(Future对象)没绑定任何行为,则这个任务永远不知道社么时候结束fut=loop.create_future()#创建一个任务(Task对象)绑定了set_after函数,函数内部在2s后,会给fut赋值#手动设置future任务的最终结果,那么fut就可以结束了await loop.create_task(set_after(fut))#等待 Future对象获取 最终结果,否则一直等下去data=await futprint(data)asyncio.run(main())
    5.4.7concurrent.futures.Future对象

    使用线程池,进程池来实现异步操作时用到的对象

    import time from concurrent.futures.thread import ThreadPoolExecutorfrom concurrent.futures.process import ProcessPoolExecutorfrom concurrent.futures import Futuredef fun(value):time.sleep(1)print(value)pool=ThreadPoolExecutor(max_workers=5)#或者pool=ProcessPoolExecutor(max_workers=5)for i in range(10):fut =pool.submit(fun,i)print(fut)
    5.4.8异步和非异步

    案例:asyncio + 不支持异步的模块

    import requestsimport asyncioasync def down_load(url):#发送网络请求,下载图片,(遇到网络下载图片的IO请求,自动化切换到其他任务)print('开始下载',url)loop=asyncio.get_event_loop()#requests模块默认不支持异步操作,所以使用线程池来配合实现了future=loop.run_in_executor(None,requets.get,url)response=await futureprint("下载完成")#图片保存本地file_name=url.rsplit('/')[-1]with open(file_name,'wwb') as fp:fp.write(response.content)if __name__=="__main__":urls=[""""""]tasks=[ down_load(url) for url in urls]+loop=asyncio.get_event_loop()loop.run_until_complete(asyncio.wait(tasks))
    5.4.9异步上下文管理器

    5.5 多任务异步协程

    import asyncio,timeasync def request(url):print("正在下载:",url)await asyncio.sleep(2)print('下载完毕!!!',url)strat_time=time.time()urls=["www.baidui.com","www.sougou.com","www.goubanjai.com"]tasks=[]for url in urls:c =request(url)task=asyncio.ensure_future(c)tasks.append(task)loop=asyncio.get_event_loop()#需要将任务列表封装到waith中loop.run_until_complete(asyncio.wait(tasks))print(time.time()-strat_time)

    5.6aiohttp模块

    #requests.get()#同步代码,——>异步操作aiohttp#pip install aiohttpimport asyncioimport aiohttpimport aiofilesurls=["http://kr.shanghai-jiuxin.com/file/2020/0605/819629acd1dcba1bb333e5182af64449.jpg","http://kr.shanghai-jiuxin.com/file/2022/0515/small287e660a199d012bd44cd041e8483361.jpg","http://kr.shanghai-jiuxin.com/file/2022/0711/small72107f2f2a97bd4affd9c52af09d5012.jpg"]async def aiodownload(url):# s = aiohttp.ClientSession() 《=====》requests#requests.get .post#s.get s.postimg_name=url.split('/')[-1]async with aiohttp.ClientSession() as session:data=await session.get(url)async with aiofiles.open(img_name,'wb') as fp:fp.write(data) # with open(img_name,'wb') as fp:#fp.write(await data.content.read())#读取内容是异步的,需要await挂起print(img_name,'搞定')async def main():tasks=[]for url in urls:tasks.append(aiodownload(url))await asyncio.wait(tasks)'''tasks=[]for url in urlstask_obj=asyncio.ensure_future(download(url))tasks.append(task_obj)await asyncio.wait(tasks)'''if __name__=="__main__":asyncio.run(main())#loop=asyncio.get_event_loop()#loop.run_until_complete(main())

    (六)动态加载数据处理

    什么是selenium?* (1) Selenium是一个用于web应用程序测试的工具。* (2) Selenium测试直接运行在浏览器中,就像滇正的用户在操作一样。1* (3) 支持通过各种driver(FirfoxDriver,IternetExplorerDriver,OperaDriver,ChromeDriver)驱动真实浏览器完成测试* (4) seleniumt也是支持无界面浏览器操作的。 为什么使用selenium?模拟浏览器功能,自动执行网页中的js代码,实现动态加载

    selenium

    6.0selenium模块使用
    • 便捷获取网站动态加载的数据
    • 实现模拟登陆

    selenium是基于浏览器自动化的一个模块
    使用流程

    • 先打开chrome 输入 chrome://version/来查看chrome版本

    chromedriver驱动,,驱动下载好放在当前目录下就可以了

    查看驱动和浏览器版本的映射关系

    chromedriver官方文档

    浏览迷:各种浏览器版本

    安装selenium

    pip install selenium

    实例化一个浏览器对象:

    from selenium import webdriver#实例化一个浏览器对象,(传入浏览器的驱动生成)bro = webdriver.Chrome(executable_path = './chromedriver')

    方法


    #发起请求 get(url)#标签定位 find_element 新版本需要导入from selenium.webdriver.common.by import By#标签交互(搜索)send_keys('xxxx')#点击 click()#获取网页代码 page_source#执行js程序 excute_script(JS代码)#前进,后退 fowawrd() back()#关闭浏览器 quit()#对当前页面进行截图裁剪 保存, bro.save_screenshot('a.jpg')

    获取文本和标签属性的方法

    # driver: 是之前定义的打开浏览器的 “变量名称”# .text: 是获取该标签位置的文本# .get_attribute(value).:获取标签属性# value:属性字段名

    使用获取红楼梦章节标题

    from selenium import webdriverfrom lxml import etreefrom time import sleep#实例化浏览器驱动对象,drive = webdriver.Chrome()#让浏览器发送一个指定url请求drive.get("https://www.shicimingju.com/book/hongloumeng.html")#page_source获取浏览器当前页面的页面代码,(经过数据加载以及JS执行之后的结果的html结果),#我们平时打开页面源代码看得数据 和 点击检查在elements中看得代码是不一样的,有些是经过动态加载,在页面源代码代码里面不显示page_text=drive.page_source#解析红楼梦章节标题tree=etree.HTML(page_text)list_data=tree.xpath('//p[@class="book-mulu"]/ul/li')for i in list_data:article_title=i.xpath('./a/text()')[0]print(article_title)sleep(5)#关闭浏览器drive.quit()
    6.1其他自动化操作
    selenium的元素定位?元素定位:自动化要做的就是模拟鼠标和键盘来操作来操作这些元素,点击、输入等等。操作这些元素前首先要找到它们,WebDriver提供很多定位元素的方法 访问元素信息获取元素属性.get_attribute('class')获取元素文本text获取id.id获取标签名.tag_name #访问元素信息from selenium import webdriverfrom selenium.webdriver.common.by import Bybrowser = webdriver.Chrome('chromedriver.exe')browser.get('https://www.baidu.com')input = browser.find_element(by=By.ID, value='su')print(input.get_attribute('class')) # 属性值print(input.tag_name) # 标签名字a=browser.find_element(by=By.LINK_TEXT,value='新闻')print(a.text) #访问交互from selenium import webdriverfrom time import sleepfrom selenium.webdriver.common.by import Bydrive=webdriver.Chrome()drive.get('https://www.taobao.com/')# print(drive.title)#标签定位search_input =drive.find_element(By.ID,'q')#标签交互search_input.send_keys('Iphone ')#执行一组js程序drive.execute_script("window.scrollTo(0,document.body.scrollHeight)")sleep(3)#点击搜索按钮bottom = drive.find_element(By.CSS_SELECTOR,'.btn-search')bottom.click()#在访问一个urldrive.get('http://www.baidu.com')sleep(2)#回退drive.back()sleep(2)#前进drive.forward()sleep(2)# 关闭浏览器sleep(4)drive.quit()
    6.2拉钩网自动化操作
    from selenium.webdriver import Chromeimport timefrom selenium.webdriver.common.keys import Keysfrom selenium.webdriver.common.by import Bydrive = Chrome()drive.get('https://www.lagou.com/')#点击全国quanguo=drive.find_element(by=By.XPATH,value='//*[@id="changeCityBox"]/p[1]/a')quanguo.click()#定位到搜索框, 输入python, 输入回车/点击搜索按钮drive.find_element(by=By.XPATH,value='//*[@id="search_input"]').send_keys('Python',Keys.ENTER)#得到职位,薪资,公司名称p_list=drive.find_elements(by=By.XPATH,value='//*[@id="jobList"]/p[1]/p')# print(p_list)for i in p_list:Job_name=i.find_element(by=By.XPATH,value='./p[1]//a').textJob_price=i.find_element(by=By.XPATH,value='./p[1]//span').textcompany_name=i.find_element(by=By.XPATH,value='./p[1]/p[2]//a').textprint(Job_name,Job_price,company_name)time.sleep(2)drive.quit()
    6.3窗口切换
    from selenium.webdriver import Chromeimport timefrom selenium.webdriver.common.keys import Keysfrom selenium.webdriver.common.by import Bydrive = Chrome()drive.get('https://www.lagou.com/')# 点击全国drive.find_element(by=By.XPATH, value='//*[@id="changeCityBox"]/p[1]/a').click()time.sleep(1)# 定位到搜索框, 输入python, 输入回车/点击搜索按钮drive.find_element(by=By.XPATH, value='//*[@id="search_input"]').send_keys('Python', Keys.ENTER)time.sleep(1)drive.find_element(by=By.XPATH, value='//*[@id="jobList"]/p[1]/p[1]/p[1]/p[1]/p[1]/a').click()#如何进入到新窗口中进行提取# 在selenium眼中,新窗口默认是不切换过来的drive.switch_to.window(drive.window_handles[-1])#在新窗口中提取内容job_detail = drive.find_element(by=By.XPATH,value='//*[@id="job_detail"]/dd[2]').textprint(job_detail)#关闭子窗口中drive.close()#变更窗口视角,回到原来的窗口drive.switch_to.window(drive.window_handles[0])print(drive.find_element(by=By.XPATH, value='//*[@id="jobList"]/p[1]/p[1]/p[1]/p[1]/p[1]/a').text)time.sleep(2)drive.quit()
    6.4动作链和iframe的处理
    from selenium.webdriver import Chromefrom selenium.webdriver.common.by import By# 导入动作链对应的类from selenium.webdriver import ActionChainsfrom time import sleepbro = Chrome()bro.get('')# 如果定位的标签是存在于iframe标签中的则必须通过如下操作进行标签定位bro.switch_to.frame(id) # 切换浏览器标签定位的作用域p = bro.find_element(By.ID, 'draggable')#从iframe回到原页面用default_content()# bro.switch_to.default_content() #切换到原页面#动作链action =ActionChains(bro)#点击长按指定的标签action.click_and_hold(p)#长按,for i in range(5):#perform()立即执行动作链操作#move_by_offset(x,y)x表示水平方向,y表示竖直方向action.move_by_offset(17,0).perform()#移动sleep(0.3)#释放动作链action.release()#关闭浏览器bro.quit()
    6.5模拟登录qq空间
    from selenium.webdriver import Chromefrom selenium.webdriver.common.by import Byfrom time import sleepfrom selenium.webdriver import ActionChains#创建浏览器对象bro =Chrome()#发送请求bro.get('https://qzone.qq.com/')#定位到密码登录标签,遇到iframe切换作用域#切换作用域,定位标签bro.switch_to.frame('login_frame')a_tag=bro.find_element(By.ID,'switcher_plogin')a_tag.click()#定位输入账户的标签username = bro.find_element(By.ID,'u')#定位到输入密码的标签password = bro.find_element(By.ID,'p')#输入账户username.send_keys('318129549')sleep(1)#输入密码password.send_keys('song027..')sleep(1)#定位 登录 标签login= bro.find_element(By.ID,'login_button')#点击login.click()sleep(3)bro.quit()

    Phantomjs

    1.什么是Phantomjs?(1)是一个无界面的浏览器(2)支持页面元素查找,js的执行等(3)由于不进行css和gui渲染,运行效率要比真实的浏览器要快很多 2.如何使用Phantomjs?#除了引用的文件不一样,其他的跟selenium一致(1)获取Phantoms.exe文件路径path(2)browser=webdriver.PhantomJs(path)(3)browser.get(url)

    selenium新版本已经不能导入Phantomjs了

    知道有这个东西就OK

    Chrome handless

    固定配置
    # 固定配置from selenium.webdriver import Chrome#实现无可视化界面from selenium.webdriver.chrome.options import Options# 创建一个参数对象,用来控制chrome以无界面模式打开chrome_options = Options()chrome_options.add_argument('--headless')chrome_options.add_argument('--dissable-gpu')path=r'C:\Users\456\AppData\Local\Google\Chrome\Application\chrome.exe'chrome_options.binary_location=path#path是自己电脑谷歌浏览器的位置browser = Chrome(chrome_options=chrome_options)
    代码封装
    # 代码封装from selenium.webdriver import Chromefrom selenium.webdriver.chrome.options import Optionsdef share_browser():# 创建一个参数对象,用来控制chrome以无界面模式打开chrome_options = Options()chrome_options.add_argument('--headless')chrome_options.add_argument('--dissable-gpu')path=r'C:\Users\31812\AppData\Local\Google\Chrome\Application\chrome.exe'chrome_options.binary_location=pathbrowser= Chrome(chrome_options=chrome_options)return browserbrowser=share_browser()url='https://www.baidu.com'browser.get(url)browser.save_screenshot('baidu.png')
    谷歌无头浏览器+反检测
    # 无可视化界面(无头浏览器) from selenium.webdriver import Chromefrom time import sleep#实现无可视化界面from selenium.webdriver.chrome.options import Options#实现规避检测from selenium.webdriver import ChromeOptions# 创建一个参数对象,用来控制chrome以无界面模式打开chrome_options = Options()chrome_options.add_argument('--headless')chrome_options.add_argument('--dissable-gpu')#实现规避检测options=ChromeOptions()options.add_experimental_option('excludeSwitches',['enable-outomation'])bro = Chrome(chrome_options=chrome_options,options=options)# 上网bro.get('https://www.baidu.com')print(bro.page_source)sleep(2)# 关闭浏览器bro.quit()

    (七)scrapy框架

    什么是scrapy?Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架,可以应用在包括数据挖掘,信息处理或存储历史数据等一系列的程序中。

    scrapy创建以及运行

    1.创建scrapyl项目:终端输入scrapy startproject 项目名称#项目名不能以数字开头,不能包含中文 2.项目组成spidersinit_·py自定义的爬虫文件.py--》由我们自己创建,是实现爬虫核心功能的文件__init_.pyitems.py--》定义数据结构的地方,是一个继承自scrapy.Item的类middlewares.py--》中间件代理pipelines.py--》管道文件,里面只有一个类,用于处理下载数据的后续处理默认是300优先级,值越小优先级越高(1-1000)settings.py--》配置文件比如:是否遵守robotsi协议,User-Agent定义等 3.创建爬虫文件:(1)跳转到spiders文件夹cd 项目名字/项目名字/spiders(2)scrapy genspider 爬虫名字网页的域名爬虫文件的基本组成:继承scrapy.Spider类name ='baidu'---》运行爬虫文件时使用的名字allowed domains---》爬虫允许的域名,在爬取的时候,如果不是此域名之下的url,会被过滤掉start_urls--》声明了爬虫的起始地址,可以写多个u1,一般是一个parse(self,response)--》解析数据的回调函数response.text--》响应的是字符串response.body--》响应的是二进制文件response.xpath()--》xpath方法的返回值类型是selector列表extract()--》提取的是selector对象的data属性值extract_first() --》提取的是selector列表中的第一个数据 4.运行爬虫代码scrapy crawl 爬虫名字 (--nolog)

    scrapy架构组成

    (1)引擎--》自动运行,无需关注,会自动组织所有的请求对象,分发给下载器(2)下载器--》从引擎处获取到请求对象后,请求数据(3)spiders--》Spider类定义了如何爬取某个(或某些)网站。包括了爬取的动作(例如:是否跟进链接)以及如何从网页的内容中提取结构化数据(爬取item)。换句话说,Spideri就是您定义爬取的动作及分析某个网页(或者是有些网页)的地方。(4)调度器--》有自己的调度规则,无需关注(5)管道(Item pipeline)--》最终处理数据的管道,会预留接口供我们处理数据当Item在Spider中被收集之后,它将会被传递到虹tem Pipeline,一些组件会按照一定的顺序执行对缸tem的处理。每个item pipeline组件(有时称之为“Item Pipeline”)是实现了简单方法的Python类。他们接收到Item并通过它执行一些行为,同时也决定此Item是否继续通过pipeline,或是被丢弃而不再进行处理,以下是item pipelinef的一些典型应用:1.清理HTML数据2.验证爬取的数据(检查item包含某些字段)3.查重(并丢弃)4.将爬取结果保存到数据库中
    五大核心组件
  • 引擎 Scrapy
  • 用来处理整个系统的数据流处理,触发事物(框架核心)

  • 调度器 Scheduler
  • 用来接受引擎发过来的请求,压入队列中,并在引擎再次请求的时候返回,可以想象成一个URL(抓取网页的地址或者说是链接)的优先队列,由它来决定下一个要抓取的网址是什么,同时去除重复的网址

  • 下载器 Downloader
  • 用于下载网页内容,并将网页内容返回给的链接(Scrapy下载器是建立在twisted这个高效的异步模型上的)

  • 爬虫 Spider
  • 爬虫是主要干活的,用于从特定的网页中提取自己需要的信息,即所有的实体(item)用户也可以从中提取链接,让Scrapy继续抓取下一个页面

  • 项目管道 Pipeline
  • 负责处理爬虫从网页中抽取的实体,主要功能是持久化存储,验证实体的有效性,清楚不许需要的信息,当页面被爬虫解析后,将被发送到项目管道,并经过几个特定的次序处理数据

    scrapy工作原理

    scrapy shell

    1.什么是scrapy she11?Scrapy终端,是个交互终端,供您在未启动spider的情况下尝试及调试您的爬取代码。其本意是用来测试提取数据的代码,不过您可以将其作为正常的Python终端,在上面测试任何的Python代码。该终端是用来测试XPath或Css表达式,查看他们的工作方式及从爬取的网页中提取的数据。在编写您的spider时,该终端提供了交互性测试您的表达式代码的功能,免去了每次修改后运行spider的麻烦。一旦熟悉了Scrapy终端后,您会发现其在开发和调试spider时发挥的巨大作用。 2.安装ipython安装:pip install ipython简介:如果您安装了IPython,Scrapy终端将使用IPython(替代标准Python终端)。IPython终端与其他相比更为强大,提供智能的自动补全,高亮输出,及其他特性。 3.应用:直接打开cmd输入scrapy shell 域名 即可(1)scrapy shell www.baidu.com(2)scrapy shell http://www.baidu.com(3)scrapy shell "http://www.baidu.com"(4)scrapy shell "www.baidu.com"语法:(1)response对象:response.bodyresponse.textresponse.urlresponse.status(2)response的解析:response.xpath()(常用)使用xpath路径查询特定元素,返回一个selector列表对象

    yield

    1.带有yield的函数不再是一个普通函数,而是一个生成器generator,可用于迭代2.yield是一个类似return的关键字,迭代一次遇到yield时就返回yield,后面(右边)的值。重点是:下一次迭代时,从上一次迭代遇到的yield后面的代码(下一行)开始执行3.简要理解:yield就是return返回一个值,并且记住这个返回的位置,下次迭代就从这个位置后(下一行)开始案例:1.当当网(1)yield (2).管道封装 (3).多条管道下载 (4)多页数据下载2.电影天堂(1) 一个item包含多级页面的数据

    解析相关

    • selector()

    • css() 样式选择器,返回selector选择器的 可迭代对象

      • scrapy…selector.SelectorList选择器列表
      • scrapy.selector.Selector选择器
      • 样式选择器提取文本或者属性
      ::text 提取文本lis.css('h4 a::text').get()::attr("属性名") 提取属性 lis.css('img ::attr("src")').get()
    • xpath() xpath路径

    • 选择器常用方法

      • css() / xpath()
      • extract() 提取选择中所有内容,返回是Iist
      • extract_first() / get() 提取每个选择器中的内容,返回是文本

    应用

    1.1scrapy持久化存储:
    • 基于终端指令:
      • 要求:只可以将parse方法的返回值存储到本地的文本文件中
      • 要求:持久化存储对应的文本文件类型只可以是:json,jsonlines,jl,csv,xml,
      • 指令:``scrapy crawl SpiderName -o filepath`
      • 缺点:局限性强,(数据只可以存储到指定后缀的文本文件中)
    • 基于管道指令:
      • 编码流程:
        • 数据解析
        • 在item类中定义相关的属性
        • 将解析的数据封装到item 类型的对象
        • 将item类型对象提交给管道进行持久化存储的操作
        • 在管道类的process_item中要将其接收到的item对象中存储的数据进行持久化存储操作
        • 在配置文件中开启管道
      • 优点:通用性强。
    基于本地存储
    基于终端指令存储只可以将parse方法的返回值存储到本地scrapy crawl douban -o qidian.csv import scrapyclass DoubanSpider(scrapy.Spider):name = 'douban.'#allowed_domains = ['www.xxx.con']start_urls = ['https://www.qidian.com/all/']def parse(self, response):# 解析小说的作者和标题list_data=response.xpath('//p[@class="book-img-text"]/ul/li')alldata=[]for i in list_data:#xpath返回的是列表,但是列表里一定是Selector类型的对象#.extract()可以将Selector对象中的data存储的字符串提取出来#列表调用了extract()之后,则表示将列表中的每一个Selector对象中的data对应的字符串提取出来#.extract_first()将列表中第0个元素提取出来title =i.xpath('./p[2]/h2/a/text()').extract_first()author=i.xpath('./p[2]/p/a/text()')[0].extract()dic={'title':title,'author':author}alldata.append(dic)return alldata
    基于管道存储

    使用管道要在``settings.py`中开启管道

    ITEM_PIPELINES = {#管道可以有很多个,优先级的范围是1到1000,值越小,优先级越高'scrapt_dangdang_03.pipelines.ScraptDangdang03Pipeline': 300,}

    1.1爬虫文件的编写

    import scrapyfrom scrapt_dangdang_03.items import ScraptDangdang03Itemclass DangSpider(scrapy.Spider):name = 'dang'allowed_domains = ['category.dangdang.com']start_urls = ['http://category.dangdang.com/cp01.01.02.00.00.00.html']def parse(self, response):# src = '//ul[@id="component_59"]/li//img/@src'# name='//ul[@id="component_59"]/li//img/@src'# price='//ul[@id="component_59"]/li//p[3]/span[1]/text()'# 所有的selector对象都可以再次调用xpath方法li_list = response.xpath('//ul[@id="component_59"]/li')for li in li_list:# 第一张图片个其他图片的标签的属性是不一样的# 第一张图片是src,其他的dadta-originalsrc = li.xpath('.//img/@data-original').extract_first()if not src:src = li.xpath('.//img/@src').extract_first()name = li.xpath('.//img/@alt').extract_first()price = li.xpath('.//p[3]/span[1]/text()').extract_first()print(src, name, price)item = ScraptDangdang03Item(src=src, name=name, price=price)# 获取一个就交给管道yield item

    1.2 item文件的编写

    import scrapyclass ScraptDangdang03Item(scrapy.Item):# 通俗将就是要下载的数据都有什么# 图片src = scrapy.Field()# 名字name = scrapy.Field()# 价格price = scrapy.Field()

    1.3管道文件的编写

    # Define your item pipelines here## Don't forget to add your pipeline to the ITEM_PIPELINES setting# See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html# useful for handling different item types with a single interfacefrom itemadapter import ItemAdapter# 要想使用管道必须在settings中开启class ScraptDangdang03Pipeline:fp = Nonedef open_spider(self, spider):# 在爬虫开始之前就执行的一个方法print('开始爬虫'.center(20,'-'))self.fp = open('book.json', 'w', encoding='utf-8')# item就是yield后的item对象def process_item(self, item, spider):# # 每传递一个对象,就打开一个文件,对文件的操作太过频繁# with open('book.json', 'a', encoding='utf-8') as f:# # write必须是一个字符串# # 'w'会覆盖之前的文件内容# f.write(str(item))self.fp.write(str(item))return itemdef close_spider(self, spier):print('结束爬虫'.center(20, '-'))self.fp.close()
    开启多条管道
    (1)定义管道类(2)在settings.py中开启管道#settings.pyITEM_PIPELINES = {# 管道可以有很多个,优先级的范围是1到1000,值越小,优先级越高 'scrapt_dangdang_03.pipelines.ScraptDangdang03Pipeline': 300, 'scrapt_dangdang_03.pipelines.DangDangDownloadPipeline': 301,}

    案例1

    1.1爬虫文件的编写

    import scrapyfrom scrapt_dangdang_03.items import ScraptDangdang03Itemclass DangSpider(scrapy.Spider):name = 'dang'allowed_domains = ['category.dangdang.com']start_urls = ['http://category.dangdang.com/cp01.01.02.00.00.00.html']def parse(self, response):# src = '//ul[@id="component_59"]/li//img/@src'# name='//ul[@id="component_59"]/li//img/@src'# price='//ul[@id="component_59"]/li//p[3]/span[1]/text()'# 所有的selector对象都可以再次调用xpath方法li_list = response.xpath('//ul[@id="component_59"]/li')for li in li_list:# 第一张图片个其他图片的标签的属性是不一样的# 第一张图片是src,其他的dadta-originalsrc = li.xpath('.//img/@data-original').extract_first()if not src:src = li.xpath('.//img/@src').extract_first()name = li.xpath('.//img/@alt').extract_first()price = li.xpath('.//p[3]/span[1]/text()').extract_first()print(src, name, price)item = ScraptDangdang03Item(src=src, name=name, price=price)# 获取一个就交给管道yield item

    1.2item文件的编写

    import scrapyclass ScraptDangdang03Item(scrapy.Item):# 通俗将就是要下载的数据都有什么# 图片src = scrapy.Field()# 名字name = scrapy.Field()# 价格price = scrapy.Field()

    1.3管道类的编写

    # Define your item pipelines here## Don't forget to add your pipeline to the ITEM_PIPELINES setting# See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html# useful for handling different item types with a single interfacefrom itemadapter import ItemAdapter# 要想使用管道必须在settings中开启class ScraptDangdang03Pipeline:fp = Nonedef open_spider(self, spider):# 在爬虫开始之前就执行的一个方法print('开始爬虫'.center(20, '-'))self.fp = open('book.json', 'w', encoding='utf-8')# item就是yield后的item对象def process_item(self, item, spider):# # 每传递一个对象,就打开一个文件,对文件的操作太过频繁# with open('book.json', 'a', encoding='utf-8') as f:# # write必须是一个字符串# # 'w'会覆盖之前的文件内容# f.write(str(item))self.fp.write(str(item))return itemdef close_spider(self, spier):print('结束爬虫'.center(20, '-'))self.fp.close()import urllib.request# 多条管道开启# 定义管道类class DangDangDownloadPipeline:def process_item(self, item, spier):url = 'http:' + item.get('src')filename = './books' + item.get('name') + '.jpg'print('正在下载{}'.format(item.get("name")).center(20,'-'))urllib.request.urlretrieve(url=url, filename=filename)return item

    案例2

    2.1 爬虫文件的编写

    import scrapyfrom douban.items import DoubanItemclass DoubanSpider(scrapy.Spider):name = 'douban.'#allowed_domains = ['www.xxx.con']start_urls = ['https://www.qidian.com/all/']def parse(self, response):# 解析小说的作者和标题list_data=response.xpath('//p[@class="book-img-text"]/ul/li')for i in list_data:title =i.xpath('./p[2]/h2/a/text()').extract_first()author=i.xpath('./p[2]/p/a/text()')[0].extract()item = DoubanItem()item['title'] = titleitem['author']=authoryield item #将ltem提交给管道

    2.2 items文件的编写

    import scrapyclass DoubanItem(scrapy.Item):# define the fields for your item here like:title = scrapy.Field()author=scrapy.Field()

    2.3管道类的重写

    from itemadapter import ItemAdapterimport pymysqlclass DoubanPipeline:fp = None# 重写父类方法,该方法只在开始爬虫的时候只调用一次def open_spider(self, spider):print('开始爬虫')self.fp = open('./qidian.txt', 'w', encoding='utf-8')# 专门处理Item类型对象# 该方法可以接收爬虫文件提交过来的item对象# 该方法每接收一次Item就会被调用一次def process_item(self, item, spider):author = item['author']title = item['title']self.fp.write(title + ':' + author + '\n')return item#就会传递给下一个即将执行的管道类def close_spider(self, spider):print('结束爬虫')self.fp.close()# 管道文件中一个管道对应将一组数据存储到一个平台或者载体中class mysqlPipeline:conn = Nonecursor = Nonedef open_spider(self, spider):self.conn = pymysql.Connect(host='服务器IP地址', port=端口号, user='root', password=自己的密码', db='qidian',charset='utf8')def process_item(self, item, spider):self.cursor = self.conn.cursor()try:self.cursor.execute('insert into qidian values(0,"%s","%s")'%(item["title"],item["author"]))self.conn.commit()except Exception as e:print(e)self.conn.rollback()return itemdef close_spider(self, spider):self.cursor.close()self.conn.close()# 爬虫文件提交的item类型的对象最终会提交给哪一个管道类哪
    1.2基于Spider的全站数据解析爬取
    • 就是将网站中某板块下的全部页码对应的页面数据进行爬取
    • 自行手动进行请求发送数据(推荐)
      • 手动发送请求
        • yield scrapy.Resquest(url,callback) callback专门用作数据解析

    案例1.

    import scrapyclass QidianSpider(scrapy.Spider):name = 'qidian'#allowed_domains = ['www.xxx.com']start_urls = ['https://www.qidian.com/all/']#设置一个通用的urlurl='https://www.qidian.com/all/page%d/'page_num=2def parse(self, response):list_data=response.xpath('//p[@id="book-img-text"]/ul/li')for i in list_data:title =i.xpath('./p[2]/h2/a/text()').extract_first()author=i.xpath('./p[2]/p/a[1]/text()')[0].extract()styple=i.xpath('./p[2]/p/a[2]/text()').extract_first()styple1=i.xpath('./p[2]/p/a[3]/text()').extract_first()type=styple+'.'+styple1#print(title,author,type)if self.page_num<=3:new_url=format(self.url%self.page_num)self.page_num+=1#手动的发送请求callback回调函数是专门用于数据解析的yield scrapy.Request(url=new_url,callback=self.parse)

    案例2:当当网青春文学书籍图片下载

    import scrapyfrom scrapt_dangdang_03.items import ScraptDangdang03Itemclass DangSpider(scrapy.Spider):name = 'dang'allowed_domains = ['category.dangdang.com']start_urls = ['http://category.dangdang.com/cp01.01.02.00.00.00.html']base_url = 'http://category.dangdang.com/pg'page = 1def parse(self, response):# src = '//ul[@id="component_59"]/li//img/@src'# name='//ul[@id="component_59"]/li//img/@src'# price='//ul[@id="component_59"]/li//p[3]/span[1]/text()'# 所有的selector对象都可以再次调用xpath方法li_list = response.xpath('//ul[@id="component_59"]/li')for li in li_list:# 第一张图片个其他图片的标签的属性是不一样的# 第一张图片是src,其他的dadta-originalsrc = li.xpath('.//img/@data-original').extract_first()if not src:src = li.xpath('.//img/@src').extract_first()name = li.xpath('.//img/@alt').extract_first()price = li.xpath('.//p[3]/span[1]/text()').extract_first()print(src, name, price)item = ScraptDangdang03Item(src=src, name=name, price=price)# 获取一个就交给管道yield itemif self.page < 100:self.page += 1url = self.base_url + str(self.page) + '-cp01.01.02.00.00.00.html'# url就是请求地址# callback是要执行的 函数,不需要加括号yield scrapy.Request(url=url, callback=self.parse)
    1.3请求传参
    • 使用场景:如果爬取解析的数据不在同一张页面中(深度爬取),
      将item 传递给请求所对应的回调函数
      需要将不同解析方法中获取的解析数据封装存储到同一个Item中,并且提交到管道

    案例:起点小说网

  • 爬虫文件的编写
  • import scrapyfrom qidian2.items import Qidian2Itemclass QidianSpider(scrapy.Spider):name = 'qidian'#allowed_domains = ['www.xxx.com']start_urls = ['https://www.qidian.com/all/']#解析详情页的信息def detail_parse(self,response):item=response.meta['item']detail_data=response.xpath('//p[@class="book-intro"]/p//text()').extract()detail_data=''.join(detail_data)item['detail_data']=detail_datayield item# print(detail_data)#解析小说名称和详情页的链接地址def parse(self, response):list_data=response.xpath('//p[@class="book-img-text"]/ul/li')for i in list_data:item = Qidian2Item()title=i.xpath('./p[2]/h2/a/text()').extract_first()item['title']=titledetail_url='https:'+i.xpath('./p[2]/h2/a/@href').extract_first()#发起手动请求yield scrapy.Request(url=detail_url,callback=self.detail_parse,meta={'item':item})#请求传参,meta={},可以将meta字典传递给请求所对应的回调函数
  • items文件
  • # Define here the models for your scraped items# See documentation in:# https://docs.scrapy.org/en/latest/topics/items.htmlimport scrapyclass Qidian2Item(scrapy.Item):# define the fields for your item here like:title= scrapy.Field()detail_data=scrapy.Field()pass
  • pipelines文件中管道类的重写
  • # Define your item pipelines here## Don't forget to add your pipeline to the ITEM_PIPELINES setting# See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html# useful for handling different item types with a single interfacefrom itemadapter import ItemAdapterclass Qidian2Pipeline:fp=Nonedef open_spider(self,spider):print('开始爬虫!!!!!!!!!!!!!')self.fp=open('./qidian.txt','w',encoding='utf-8')def process_item(self, item, spider):title=item['title']detail_data=item['detail_data']self.fp.write(title+":"+detail_data+"\n")return itemdef close_spider(self,spider):print('结束爬虫!!!!!!!!!!!!')self.fp.close()

    图片管道

    图片数据爬取之自定义ImagePipeline

    三个核心的方法:

    • get_media_requests(elf,item,info)根据item中图片连接的属性,返回图片下载的request
    • file_path(self,request,response,info)根据请求和响应返回图片保存的位置(相对于IMAGES STORE)
    • item_completed(self,results,.item,info)图片下载完成后,从results的结果获取图片的保存路径,并设置到item中,最后返回这个item

    案例一

    * 基于scrapy爬取字符串类型数据和爬取图片类型数据区别:- 字符串: 只需要基于xpath 进行解析,且提交管道进行持久化存储- 图片: 只可以通过xpath解析出图片的src属性,单独的对图片地址发请求获取图片二进制类型的数据。* 基于ImagePipeline:- 只需要将img的src属性值进行解析,提交到管道,管道就会对图片src进行发送请求获取图片二进制类型的数据,且还会帮我么进行持久化存储。* 需求:爬取站长素材中高清图片- 使用流程:1. 数据解析(图片地址)2. 将存储图片地址的item提交到指定的管道类中3. 在管道文件中自定义一个基于ImagesPipeline的一个管道类- get_media_requests()- file_path()- item_completed()4. 在配置文件中:- 指定存储图片的目录:IMAGES_STORE='./imgs_站长素材'- 开启自定义的管道:

    1. 爬虫文件编写解析数据

    #站长素材 高清图片的解析import scrapyfrom imgPro.items import ImgproItemclass ImgSpider(scrapy.Spider):name = 'img'#allowed_domains = ['www.xxx.com']start_urls = ['https://sc.chinaz.com/tupian/']def parse(self, response):p_list= response.xpath('//p[@id="container"]/p')for i in p_list:img_address='https:'+i.xpath('./p/a/img/@src').extract_first()#print(img_address)#实例化item对象item=ImgproItem()#传入img_address这个属性,item['src']=img_address#提交item 到管道yield item

    2.把图片地址提交到管道

    #来到item 中封装一个属性scr=scrapy.Field()

    3.将解析到的数据封装到item中

    from imgPro.items import ImgproItem#实例化item对象item=ImgproItem()#传入img_address这个属性,item['img_address']=img_address#提交item 到管道yield item

    4.管道类的重写 pipelines

    import scrapyfrom scrapy.pipelines.images import ImagesPipelineclass ImagsPipeline(ImagesPipeline):#可以根据图片地址进行图片数据的请求#对item中的图片进行请求操作def get_media_requests(self,item ,info ):yield scrapy.Request(item['src'])#指定图片持久化存储的路径def file_path(self,request,response=None,info=None):img_name=request.url.split('/')[-1]return img_namedef item_completed(self,results,item ,info):return item #该返回值会传递一给下一个即将被执行的管道类

    5.settings中图片管道相关参数

    #指定图片存储目录IMAGES_STORE='./imgs_站长素材'#缩略图IMAGES_THUMBS={'small':(60,32),'big':(120,80)}

    6.settings配置

    #开启管道, 管道类名换成自定义的名称ITEM_PIPELINES = {'imgPro.pipelines.ImagsPipeline': 300,}USER_AGENT = 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36'# Obey robots.txt rulesROBOTSTXT_OBEY = False#显示报错日志 只显示错误类型的日志信息LOG_LEVEL="ERROR"```xxxxxxxxxx11 1#开启管道, 管道类名换成自定义的名称2ITEM_PIPELINES = {3 'imgPro.pipelines.ImagsPipeline': 300,4}5USER_AGENT = 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36'67# Obey robots.txt rules8ROBOTSTXT_OBEY = False910#显示报错日志 只显示错误类型的日志信息11LOG_LEVEL="ERROR"python

    案例二

    • 爬虫文件
    import scrapyfrom scrapy.linkextractors import LinkExtractorfrom scrapy.spiders import CrawlSpider, Ruleclass BookSpider(CrawlSpider):name = 'book'allowed_domains = ['dushu.com']start_urls = ['https://dushu.com/book/']rules = (#所有图书分类css匹配Rule(LinkExtractor(restrict_css='.sub-catalog'), follow=True),#分页正则规则Rule(LinkExtractor(allow='/book/\d+?_\d+?\.html'), follow=True),#图书正则详情Rule(LinkExtractor(allow='/book/\d+/'), callback='parse_item', follow=False),)def parse_item(self, response):item = {}item['name']=response.css('h1::text').get()#使用ImagePipiline下载图片时,需要使用image_urls字段,是可迭代的list或tuple类型item['cover']=response.css('.pic img::attr("src")').get()# item['images']=[]#下载图片后,保存在本地的文件位置item['price']=response.css('.css::text').get()table =response.css('#ctl00_c1_bookleft table')item['author']=table.xpath('.//tr[1]/td[2]/text()').get()item['publish']=table.xpath('.//tr[2]/td[2]/a/text()').get()table=response.css('.book-details>table')item['isbn']=table.xpath('.//tr[1]/td[2]/text()').get()item['publish_date']=table.xpath('.//tr[1]/td[4]/text()').get()item['pages']=table.xpath('.//tr[2]/td[4]/text()').get()yield item
    • pipeline
    # Define your item pipelines here## Don't forget to add your pipeline to the ITEM_PIPELINES setting# See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html# useful for handling different item types with a single interfacefrom itemadapter import ItemAdapterfrom scrapy.pipelines.images import ImagesPipelinefrom scrapy import Requestclass DushuPipeline:def process_item(self, item, spider):return itemclass BookImagePipiline(ImagesPipeline):DEFAULT_IMAGES_URLS_FIELD = 'cover'DEFAULT_IMAGES_RESULT_FIELD = 'path'def get_media_requests(self, item, info):return Request(item.get('cover'),meta={'book_name':item['name']})def file_path(self, request, response=None, info=None, *, item=None):name=request.meta['book_name']return f'{name}.jpg'def item_completed(self, results, item, info):item['path']=[ v['path'] for k,v in results if k]return item
    • settings
    import os.pathBOT_NAME = 'dushu'SPIDER_MODULES = ['dushu.spiders']NEWSPIDER_MODULE = 'dushu.spiders'USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36'ROBOTSTXT_OBEY = FalseDEFAULT_REQUEST_HEADERS = {'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8','Accept-Language': 'en','Referer':'https://www.dushu.com/book/1114_13.html'}ITEM_PIPELINES = {'dushu.pipelines.DushuPipeline': 300,'dushu.pipelines.BookImagePipiline':302}#配置图片管道的相关参数BASE_DIR=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))IMAGES_STORE=os.path.join(BASE_DIR,'images')IMAGES_THUMBS={'small':(60,32),'big':(120,80)}

    CrawlSpider

    1.继承自scrapy.Spider2.独门秘笈CrawlSpider可以定义规则,再解析html内容的时候,可以根据链接规则提取出指定的链接,然后再向这些链接发送请求所以,如果有需要跟进链接的需求,意思就是爬取了网页之后,需要提取链接再次爬取,使用CrawlSpider是非常合适的 3.提取链接链接提取器,在这里就可以写规则提取指定链接scrapy.linkextractors.LinkExtractor(a11ow=(),#正则表达式提取符合正则的链接deny =()#(不用)正则表达式不提取符合正则的链接allow_domains =()#(不用)允许的域名deny_domains =()#(不用)不允许的域名restrict xpaths=(),#xpath,提取符合xpathi规则的链接restrict_css =()#提取符合选择器规则的链接)4.模拟使用正则用法:1inks1 = LinkExtractor(allow=r'1ist23_\d+\.html')xpath:links2 = LinkExtractor(restrict_xpaths=r'//p[@class="x"]')css用法:links3 = LinkExtractor(restrict_css='.X')5.提取连接link.extract_links(response) 6.注意事项【注1】callback只能写函数名字符串,cal1back='parse_item'【注2】在基本的spider中,如果重新发送清求,那里的cal1back写的是 cal1back=self.parse_item【注3】follow=true是否跟进就是按照提取连接规则进行提取

    CrawlSpider案例

    案例一:读书网数据入库

    l.创建项目:scrapy startproject dushuproject2.跳转到spiders路径 cd\dushuproject\dushuproject\spiders3.创建爬虫类:scrapy genspider -t crawl read www.dushu.com4.items5.spiders6.settings7.pipelines数据保存到本地数据保存到mysq1数据库

    数据入库:

    (1)settings配置参数:DBH0T='127.0.0.1'DB_PORT =3306DB USER 'root'DB PASSWORD '1234'DB_NAME ='readbood'DB_CHARSET = 'utf8'(2)管道配置#加载配置文件from scrapy.utils.project import get_project_settingsimport pymysqlclass MysqlPipeline(object):#__init__方法和open_spider的作用是一样的#init获取settings中的连接参数

    代码实现:

    • 爬虫文件
    import scrapyfrom scrapy.linkextractors import LinkExtractorfrom scrapy.spiders import CrawlSpider, Rulefrom scrapy_readbook_05.items import ScrapyReadbook05Itemclass ReadSpider(CrawlSpider):name = 'read'allowed_domains = ['www.dushu.com']start_urls = ['https://www.dushu.com/book/1188_1.html']rules = (Rule(LinkExtractor(allow=r'/book/1188_\d+\.html'), callback='parse_item', follow=True),)def parse_item(self, response):img_list=response.xpath('//p[@class="bookslist"]/ul/li//img')for img in img_list:src=img.xpath('./@data-original').extract_first()name=img.xpath('./@alt').extract_first()book=ScrapyReadbook05Item(name=name,src=src)yield book
    • item文件
    import scrapyclass ScrapyReadbook05Item(scrapy.Item):# define the fields for your item here like:# name = scrapy.Field()name=scrapy.Field()src=scrapy.Field()
    • settings.py
    DB_HOST = '127.0.0.1'DB_PORT = 3306DB_USER = 'root'DB_PASSWORD = '123.com'DB_NAME = 'readbook'DB_CHARSET = 'utf8'# Configure item pipelines# See https://docs.scrapy.org/en/latest/topics/item-pipeline.htmlITEM_PIPELINES = {'scrapy_readbook_05.pipelines.ScrapyReadbook05Pipeline': 300,'scrapy_readbook_05.pipelines.MysqlPipiline': 301,}
    • 管道实现本地存储和数据库存储
    # Define your item pipelines here## Don't forget to add your pipeline to the ITEM_PIPELINES setting# See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html# useful for handling different item types with a single interfacefrom itemadapter import ItemAdapterclass ScrapyReadbook05Pipeline:def open_spider(self, spider):self.fp = open('./book.json', 'w', encoding='utf-8')def process_item(self, item, spider):print('正在写入数据 : ' + item.get('name'))self.fp.write(str(item))return itemdef close_spider(self, spider):self.fp.close()# 加载settings文件from scrapy.utils.project import get_project_settingsimport pymysqlclass MysqlPipiline:def open_spider(self, spider):settings = get_project_settings()self.host = settings['DB_HOST']self.port = settings['DB_PORT']self.user = settings['DB_USER']self.password = settings['DB_PASSWORD']self.name = settings['DB_NAME']self.charset = settings['DB_CHARSET']self.connect()def connect(self):self.conn = pymysql.connect(user=self.user,host=self.host,port=self.port,password=self.password,database=self.name,charset=self.charset)self.cursor = self.conn.cursor()def process_item(self, item, spider):sql = 'insert into book(name,src) values("{}","{}")'.format(item['name'], item['src'])# 执行sql语句self.cursor.execute(sql)# 提交self.conn.commit()print(f'数据 : {item["name"]}提交成功......')return itemdef close_spider(self, spider):self.cursor.close()self.conn.close()

    案例二: 使用默认的ImagesPipeline下载图片

    • 爬虫文件
    import scrapyfrom scrapy.linkextractors import LinkExtractorfrom scrapy.spiders import CrawlSpider, Ruleclass BookSpider(CrawlSpider):name = 'book'allowed_domains = ['dushu.com']start_urls = ['https://dushu.com/book/']rules = (#所有图书分类css匹配Rule(LinkExtractor(restrict_css='.sub-catalog'), follow=True),#分页正则规则Rule(LinkExtractor(allow='/book/\d+?_\d+?\.html'), follow=True),#图书正则详情Rule(LinkExtractor(allow='/book/\d+/'), callback='parse_item', follow=False),)def parse_item(self, response):item = {}item['name']=response.css('h1::text').get()#使用默认的ImagePipiline下载图片时,需要使用image_urls字段,是可迭代的list或tuple类型item['image_urls']=response.css('.pic img::attr("src")').extract()item['images']=[]#下载图片后,保存在本地的文件位置item['price']=response.css('.css::text').get()table =response.css('#ctl00_c1_bookleft table')item['author']=table.xpath('.//tr[1]/td[2]/text()').get()item['publish']=table.xpath('.//tr[2]/td[2]/a/text()').get()table=response.css('.book-details>table')item['isbn']=table.xpath('.//tr[1]/td[2]/text()').get()item['publish_date']=table.xpath('.//tr[1]/td[4]/text()').get()item['pages']=table.xpath('.//tr[2]/td[4]/text()').get()yield item
    • settings.py
    USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36'ROBOTSTXT_OBEY = FalseITEM_PIPELINES = {'dushu.pipelines.DushuPipeline': 300,'scrapy.pipelines.images.ImagesPipeline':301}#配置图片管道的相关参数BASE_DIR=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))IMAGES_STORE=os.path.join(BASE_DIR,'images')DEFAULT_REQUEST_HEADERS = {'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8','Accept-Language': 'en','Referer':'https://www.dushu.com/book/1114_13.html'}

    日志信息和等级

    (1)日志级别:CRITICAL:严重错误ERROR: 一股错误WARNING:警告INFO:一般信息DEBUG:调试信息默认的日志等级是DEBUG只要出现了DEBUG或者DEBUG以上等级的日志那么这些日志将会打印(2)settings.py文件设置:默认的级别为DEUG,会显示上面所有的信息在配置文件中settings.pyL0G_FILE:将屏幕显示的信息全部记录到文件中,屏幕不再显示,注意文件后缀一定是.10gL0G_LEVEL:设置日志显示的等级,就是显示哪些,不显示哪些#settigs.py中指定日志L0G_FILE='logdemo.log'#将屏幕显示的信息全部记录到文件中,屏幕不再显示,注意文件后缀一定是.10gL0G_LEVEL='INFO'#设置日志显示的等级,就是显示哪些,不显示哪些

    scrapy的post请求

    (1)重写start_requests方法:def start_requests(self)(2)start_requests的返回值:scrapy.FormRequest(url=url,headers=headers,callback=self.parse_item,formdata=data)url:要发送的post地址headers:可以定制头信息ca11back:回调函数formdata:post所携带的数据,这是一个字典 import scrapyimport jsonclass TestpostSpider(scrapy.Spider):name = 'testpost'allowed_domains = ['fanyi.baidu.com']#post请求如果没有参数,那么这个请求就没有用了# post方法 start_urls没有用了# parse方法也没有用了# start_urls = ['https://fanyi.baidu.com/sug']## def parse(self, response):# passdef start_requests(self):url = 'https://fanyi.baidu.com/sug'data = {'kw': 'python'}yield scrapy.FormRequest(url=url, formdata=data, callback=self.parse_second)def parse_second(self, response):content = response.textobj = json.loads(content)print(obj)

    分布式爬虫

    pip install scrapy-redis

    安装服务器并执行

    docker pull redisdocker run -dit --name redis-server -p 6378:6379 redis#然后使用 docker ps 查看运行的容器

    创建爬虫项目

    scrapy startproject dushu_rediscd dushu_redis\dushu_redsi\spidersscrapy genspider guoxu dushu.com

    编写爬虫程序

    #继承RedisSpiderfrom scrapy_redis.spiders import RedisSpiderfrom scrapy import Requestclass GuoxueSpider(RedisSpider):name = 'guoxue'allowed_domains = ['dushu.com']redis_key = 'gx_start_urls'def parse(self, response):for url in response.css('.sub-catalog a::attr("href")').extract():yield Request(url='https://www.dushu.com' + url, callback=self.parse_item)def parse_item(self, response):p_list = response.css('.book-info')for i in p_list:item = {}item['name'] = i.xpath('./p//img/@alt').get()item['cover'] = i.xpath('./p//img/@src').get()item['detail_url'] = i.xpath('./p/a/@href').get()yield item# 下一页next_url=response.css('.pages ').xpath('./a[last()]/@href').get()yield Request(url='https://www.dushu.com' + next_url, callback=self.parse_item)

    settings配置

    #配置调度器SCHEDULER = 'scrapy_redis.scheduler.Scheduler'SCHEDULER_PERSIST = True# 配置去重DUPEFILTER_CLASS = 'scrapy_redis.dupefilter.RFPDupeFilter'#配置redis消息队列服务器REDIS_URL='redis://192.168.163.128:6378/0'USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36'ROBOTSTXT_OBEY = False

    配置好后,然后执行爬虫文件

    scrapy crawl guoxue

    进入到redis数据库

    docker exec -it redis-server bash root@1472cc4b0e69:/data# redis-cli127.0.0.1:6379> select 0OK127.0.0.1:6379> keys *(empty array)127.0.0.1:6379> lpush gx_start_urls https://www.dushu.com/guoxue/(integer) 1127.0.0.1:6379>
    需要做网站?需要网络推广?欢迎咨询客户经理 13272073477