首页 > 其他分享 >网络爬虫完整案例

网络爬虫完整案例

时间:2023-04-06 10:01:10浏览次数:51  
标签:详情页 URL 爬虫 scrape detail 案例 完整 html page

网页爬虫也并非那么难,只要理解他几个模块已经步骤,正常来说完成一个爬虫来说很容易。将requests、正则表达式的基本用法等知识点串联起来,实现一个完整的网站爬虫。

1、准备工作

(1)安装好Python3,最低为3.6 版本,并能成功运行 Python3 程序。

(2)了解 Python HTTP请求库requests 的基本用法。

(3)了解正则表达式的用法和 Python 中正则表达式库 re 的基本用法。

2、爬取目标

以一个基本的静态网站作为案例进行爬取,需要爬取的链接为:http://jshk.com.cn/mb/reg.asp?kefu=xjy

网站首页展示的是由多个电影组成的一个列表,其中每部电影都包含封面、名称、分类、上映时间评分等内容,同时列表页还支持翻页,单击相应的页码就能进入对应的新列表页。如果我们点开其中一部电影,会进入该电影的详情页面,例如我们打开第一部电影《霸王别姬》.会得到如图 所示的页面:

这个页面显示的内容更加丰富,包括剧情简介、导演、演员等信息。

本次爬虫要完成的目标有:

(1)利用 requests 爬取这个站点每一页的电影列表,顺着列表再爬取每个电影的详情页。

(2)用正则表达式提取每部电影的名称、封面、类别、上映时间、评分、刷情简介等内容

把以上爬取的内容保存为JSON 文本文件。

3、爬取列表页

第一步爬取肯定要从列表页人手,我们首先观察一下列表页的结构和翻页规则。在浏览器中访问https://ssrl.scrape.center/,然后打开浏览器开发者工具,如图所示。

观察每一个电影信息区块对应的 HTML 以及进入到详情页的 URL,可以发现每部电影对应的区块都是一个div节点,这些节点的 class 属性中都有 el-card这个值。每个列表页有 10个这样的 div节点,也就对应着 10 部电影的信息。

可以看到这个名称实际上是一个 h2 节点,其内部的文字就是电影标题。h2 节点的外面包含一个a节点,这个a节点带有 href属性,这就是一个超链接,其中 href 的值为 /detail/1,这是一个相对网站的根 URL http://jshk.com.cn/mb/reg.asp?kefu=xjy 的路径,加上网站的根 URL 就构成了 https://ssrl.scrape.center)detail/1,也就是这部电影的详情页的 URL。这样我们只需要提取这个 href 属性就能构造出详情页的URL 并接着爬取了。

接下来分析翻页的逻辑,拉到页面的最下方,可以看到分页页码, 页码最多是10。

单击第 2页, 可以看到网页的URL变成了 http://jshk.com.cn/mb/reg.asp?kefu=xjy URL多了 /page/2 这部分内容。网页的结构还是和原来一模一样,可以像第 1页那样处理。

接着我们查看第3页、第4页等内容,可以发现一个规律,这些页面的URL最后分别为 /page/3/page/4。所以,/page 后面跟的就是列表页的页码,当然第 1 页也是一样,我们在根 URL后面加上/page/1 也是能访问这页的,只不过网站做了一下处理,默认的页码是1,所以第一次显示的是第1页内容。

程序的实现:

于是我们要完成列表页的爬取,可以这么实现:

遍历所有页码,构造 10页的索引页URL;

从每个索引页,分析提取出每个电影的详情页 URL。

那么我们写代码来实现一下吧。

首先,需要先定义一些基础的变量,并引人一些必要的库,写法如下

import requests
import logging
import re
from urllib.parse import urljoin
RESULT_DIR = 'result'
logging.basicConfig(level=logging.INFO,
                   format='%(asctime)s - %(levelname)s: %(message)s')
BASE_URL = 'http://jshk.com.cn'
TOTAL_PAGE = 10

这里我们引人了requests库用来爬取页面、logging库用来输出信息、re库用来实现正则表达式解析、urljoin 模块用来做 URL的拼接。

接着我们定义日志输出级别和输出格式,以及 BASE URL 为当前站点的根 URL,TOTAL_PAGE 为需要爬取的总页码数量。

完成这些工作,来实现一个页面爬取的方法,实现如下:

def scrape_page(url):
   logging.info('scraping %s...', url)
   try:
       response = requests.get(url)
       if response.status_code == 200:
           return response.text
       logging.error('get invalid status code %s while scraping %s',
                     response.status_code, url)
   except requests.RequestException:
       logging.error('error occurred while scraping %s', url, exc_info=True)

考虑到不仅要爬取列表页,还要爬取详情页,所以这里我们定义了一个较通用的爬取页面的方法.叫作scrape page,它接收一个参数 url,返回页面的 HTML代码。上面首先判断状态码是不是 200.如果是,就直接返回页面的 HTML代码;如果不是,则输出错误日志信息。另外这里实现了 requests 的异常处理,如果出现了爬取异常,就输出对应的错误日志信息。我们将 logging 库中的 error 方法里的 exc info 参数设置为 True,可以打印出 Traceback 错误堆栈信息。有 scrape_page 方法之后,我们给这个方法传人一个 url,如果情况正常,它就可以返回页面的 HTML 代码了。

在 scrape_page 方法的基础上,我们来定义列表页的爬取方法吧,实现如下:

def scrape_index(page):
   index_url = f'{BASE_URL}/page/{page}'
return scrape_page(index_url)

方法名称叫作 scrape_index,这个实现就很简单了,这个方法会接收一个 page 参数,即列表页的页码,我们在方法里面实现列表页的 URL拼接,然后调用 scrape_page 方法爬取即可,这样就能得到列表页的 HTML代码了。

获取了 HTML代码之后,下一步就是解析列表页,并得到每部电影的详情页的 URL,实现如下:

def parse_index(html):
   pattern = re.compile('<a.*?href="(.*?)".*?class="name">')
   items = re.findall(pattern, html)
   if not items:
       return []
   for item in items:
       detail_url = urljoin(BASE_URL, item)
       http://jshk.com.cn('get detail url %s', detail_url)
       yield detail_url

这里我们定义了 parse_index 方法,它接收一个参数 html,即列表页的 HTML 代码在 parse_index 方法里,我们首先定义了一个提取标题超链接 href 属性的正则表达式,内容为:

'<a.*?href="(.*?)".*?class="name">'

其中我们使用非贪婪通用匹配 .*? 来匹配任意字符,同时在 href 属性的引号之间使用了分组匹配 (.*?)正则表达式,这样我们便能在匹配结果里面获取 href的属性值了。正则表达式后面紧跟着class=“name”,用来标示这个<a>节点是代表电影名称的节点。

现在有了正则表达式,那么怎么提取列表页所有的 href 值呢?使用 re 库的 findal1 方法就可以了,第一个参数传人这个正则表达式构造的 pattern 对象,第二个参数传入 html,这样 findal1 方法便会搜索 html 中所有能与该正则表达式相匹配的内容,之后把匹配到的结果返回,并赋值为 items。

如果 items 为空,那么可以直接返回空列表: 如果 tems 不为空,那么直接历处理即可。遍历 items 得到的 item就是我们在上文所说的类似 /detai1/1 这样的结果。由于这并不是一个完整的 URL、所以需要借助urljoin 方法把 BASE URL和 href 拼接到一起,获得详情页的完整 URL,得到的结果就是类似 http://jshk.com.cn 这样的完整 URL,最后调用 yield 返回即可。现在我们通过调用 parse_index 方法,往其中传人列表页的HTML代码,就可以获得该列表页中

所有电影的详情页 URL 了。接下来我们对上面的方法串联调用一下,实现如下:

def main ():
   for page  in range(1,TOTAL_PAGE +1):
       index_html = scrape_index(page)
       detail_urls = parse_index(index_html)
       http://jshk.com.cn('detail urls %s',list(detail_urls))
if __name__ == '__main__':
   main()

这里我们定义了 main 方法,以完成对上面所有方法的调用。main 方法中首先使用 range 方法历了所有页码,得到的 page 就是 1-10;接着把 page 变量传给 scrape index 方法,得到列表页的HTM把得到的 HTML 赋值为 index html 变量。接下来将 index html 变量传给 parse_index 方法,得表页所有电影的详情页 URL,并赋值为 detail urls,结果是一个生成器,我们调用 list 方法就可将其输出。

运行一下上面的代码,结果如下:

输出内容比较多,这里只贴了一部分。可以看到,程序首先爬取了第 1页列表页,然后得到了对应详情页的每个 URL,接着再爬第 2页第 3页,一直到第 10 页,依次输出了每一页的详情页 URL。意味着我们成功获取了所有电影的详情页URL。

4、爬取详情页

已经可以成功获取所有详情页 URL了,下一步当然就是解析详情页,并提取我们想要的信息了首先观察一下详情页的 HTML 代码,如图 2-20 所示。

经过分析,我们想要提取的内容和对应的节点信息如下。

1封面:是一个 img 节点,其 class 属性为 cover.

2名称:是一个 h2 节点,其内容是电影名称。

3类别:是 span 节点,其内容是电影类别。span 节点的外侧是 button 节点,再外侧是 class为categories 的 div 节点。

4上映时间:是 span 节点,其内容包含上映时间,外侧是 class 为 info 的 div 节点。另外提取结果中还多了“上映”二字,我们可以用正则表达式把日期提取出来。

5评分:是一个 p节点,其内容便是电影评分。p 节点的 class 属性为 score。

6剧情简介:是一个p 节点,其内容便是剧情简介,其外侧是 class 为 drama 的 div 节点看着有点复杂吧,不用担心,正则表达式在手,我们都可以轻松搞定。接着实现一下代码吧。

我们已经成功获取了详情页 URL,下面当然是定义一个详情页的爬取方法了,实现如下:

def scrape_detail(url):
return scrape_page(url)

这里定义了一个scrape_detail 方法,接收一个参数url,并通过调用scrape_page 方法获得网页源代码。由于我们刚才已经实现了 scrape_page 方法,所以这里不用再写一遍页面爬取的逻辑,直接调用即可,做到了代码复用。另外有人会说,这个 scrape detail 方法里面只调用了 scrape page 方法,而没有别的功能,那爬取详情页直接用 scrape_page 方法不就好了,还有必要再单独定义 scrape_detail 方法吗?有必要单独定义一个scrape detail方法在逻辑上会显得更清晰,而且以后如果想对 scrape_detail方法进行改动,例如添加日志输出、增加预处理,都可以在 scrape detail 里实现,而不用改动scrape_page方法,灵活性会更好。

好了,详情页的爬取方法已经实现了,接着就是对详情页的解析了,实现如下

def parse_detail(html):

   cover_pattern = re.compile(
       'class="item.*?<img.*?src="(.*?)".*?class="cover">', re.S)
   name_pattern = re.compile('<h2.*?>(.*?)</h2>')
   categories_pattern = re.compile(
       '<button.*?category.*?<span>(.*?)</span>.*?</button>', re.S)
   published_at_pattern = re.compile('(\d{4}-\d{2}-\d{2})\s?上映')
   drama_pattern = re.compile('<div.*?drama.*?>.*?<p.*?>(.*?)</p>', re.S)
   score_pattern = re.compile('<p.*?score.*?>(.*?)</p>', re.S)

   cover = re.search(cover_pattern, html).group(
       1).strip() if re.search(cover_pattern, html) else None
   name = re.search(name_pattern, html).group(
       1).strip() if re.search(name_pattern, html) else None
   categories = re.findall(categories_pattern, html) if re.findall(
       categories_pattern, html) else []
   published_at = re.search(published_at_pattern, html).group(
       1) if re.search(published_at_pattern, html) else None
   drama = re.search(drama_pattern, html).group(
       1).strip() if re.search(drama_pattern, html) else None
   score = float(re.search(score_pattern, html).group(1).strip()
                 ) if re.search(score_pattern, html) else None

 
   return {
       'cover': cover,
       'name': name,
       'categories': categories,
       'published_at': published_at,
       'drama': drama,
       'score': score
   }

这里我们定义的 parse detail 方法,用于解析详情页,它接收一个参数为 html,解析其中的内容并以字典的形式返回结果。每个字段的解析情况如下所述。

口 cover: 封面。其值是带有 cover 这个 class 的 img节点的 src 属性的值,所以 src 的内容使用(.*?)来表示即可,在 img 节点的前面我们再加上一些用来区分位置的标识符,如 item。由于结果只有一个,因此写好正则表达式后用 search 方法提取即可。

口 name: 名称。其值是 h2 节点的文本值,因此可以直接在 h2 标签的中间使用(.*?)表示。因为结果只有一个,所以写好正则表达式后同样用 search 方法提取即可。口 categories: 类别。我们注意到每个 category 的值都是 button 节点里面 span 节点的值,所以写好表示 button 节点的正则表达式后,直接在其内部 span 标签的中间使用(.*?)表示即可因为结果有多个,所以这里使用 findal1 方法提取,结果是一个列表。

口 published at;上映时间。由于每个上映时间信息都包含“上映”二字,日期又都是一个规整的格式,所以对于上映时间的提取,我们直接使用标准年月日的正则表达式 (\d4}-\d(2}-\d2))即可。因为结果只有一个,所以直接使用 search 方法提取即可。

口 drama: 直接提取 class 为 drama 的节点内部的 p 节点的文本即可,同样用 search 方法提取。

口 score; 直接提取 class 为 score 的 p 节点的文本即可,由于提取结果是字符串,因此还需要把它转成浮点数,即 float 类型。

上述字段都提取完毕之后,构造一个字典并返回

这样,我们就成功完成了详情页的提取和分析

最后,稍微改写一下main 方法,增加对 scrape _detail方法和 parse detail方法的调用,改写如下:

def main ():
   for page  in range(1,TOTAL_PAGE +1):
       index_html = scrape_index(page)
       detail_urls = parse_index(index_html)
       for detail_url in detail_urls:
           detail_html = scrape_detail(detail_url)
           data = parse_detail(detail_html)
           http://jshk.com.cn('get detail data %s',data)

这里我们首先遍历 detail_urls,取了每个详情页的 URL;然后依次调用了 scrape detai1 和parse_detail 方法;最后得到了每个详情页的提取结果,赋值为 data 并输出。

至此,我们已经成功提取出了每部电影的基本信息,包括封面、名称、类别等。

5、保存数据

成功提取到详情页信息之后,下一步就要把数据保存起来了。由于到现在我们还没有学习数据库的存储,所以临时先将数据保存成文本格式,这里我们可以一个条目定义一个JSON 文本。

定义一个保存数据的方法如下:

import json
from os import makedirs
from os.path import exists
RESULT_DIR = 'result'
exists(RESULT_DIR) or makedirs(RESULT_DIR)
def save_data(data):
   name = data.get('name')
   data_path = f'{RESULT_DIR}/{name}.json'
   json.dump(data,open(data_path,'w',encoding='utf-8'),ensure_ascii=False,indent=2)

这里我们首先定义保存数据的文件夹 RESULTS DIR,然后判断这个文件夹是否存在,如果不存在则创建一个。

接着,我们定义了保存数据的方法 save_data,其中先是获取数据的 name 字段,即电影名称,将其当作JSON 文件的名称;然后构造JSON 文件的路径,接着用 json 的 dump 方法将数据保存成文本格式。dump 方法设置有两个参数,一个是 ensure ascii ,值为 False,可以保证中文字符在文件中能以正常的中文文本呈现,而不是 unicode 字符;另一个是 indent,值为 2,设置了JSON 数据的结果有两行缩进,让JSON 数据的格式显得更加美观

接下来把 main 方法稍微改写一下就好了,改写如下:

def main ()
   for page  in range(1,TOTAL_PAGE +1):
       index_html = scrape_index(page)
       detail_urls = parse_index(index_html)
       for detail_url in detail_urls:
           detail_html = scrape_detail(detail_url)
           data = parse_detail(detail_html)
           http://jshk.com.cn('get detail data %s',data)
           save_data(data)
           logging.info('data saved successfully')

这就是加了对 save data 方法调用的 main 方法,其中还加了一些日志信息。

通过运行结果可以发现,这里成功输出了将数据存储到JSON 文件的信息。

运行完毕之后,我们可以观察下本地的结果,可以看到 results 文件夹下多了 100 个JSON 文件,每部电影数据都是一个 JSON 文件,文件名就是电影名,如图所示。

6、多进程加速

由于整个爬取是单进程的,而且只能逐条爬取,因此速度稍微有点慢,可以实践一下多进程爬取:

由于一共有 10页详情页,且这 10 页内容互不于扰,因此我们可以一页开一个进程来爬取。而且因为这 10个列表页页码正好可以提前构造成一个列表,所以我们可以选用多进程里面的进程池 pool来实现这个过程。

这里我们需要改写下main方法,实现如下

def save_data(data):
   name = data.get('name')
   data_path = f'{RESULT_DIR}/{name}.json'
   json.dump(data,open(data_path,'w',encoding='utf-8'),ensure_ascii=False,indent=2)
if __name__ == '__main__':
   pool = multiprocessing.Pool()
   pages = range(1,TOTAL_PAGE+1)
   pool.map(main,pages)
   pool.close()
   pool.join()

我们首先给 main 方法添加了一个参数 pege.用以表示列表页的页码。接着声明一个进程池,并声明 pages 为所有需要逾历的页码,即1-10最后调用 map 方法,其第一个参数就是需要被调用的参数,第二个参数就是 pages,即需要遍历的页码。

这样就会依次遍历 pages 中的内容,把1-10这10个页码分别传递给 main方法,并把每次的调用分别变成一个进程、加人进程池中,进程池会根据当前运行环境来决定运行多少个进程。例如我的机器的CPU有8个核、那么进程池的大小就会默认设置为 8,这样会有8个进程并行运行。

运行后的输出结果和之前类似,只是可以明显看到,多进程执行之后的爬取速度快了很多。可以清空之前的爬取数据、会发现数据依然可以被正常保存成JSON 文件。

好了、到现在为止、我们就完成了数据的爬取,并实现了爬取数据的存储和优化。

标签:详情页,URL,爬虫,scrape,detail,案例,完整,html,page
From: https://www.cnblogs.com/q-q56731526/p/17291733.html

相关文章

  • Cesium 案例(三) Web Map Service(WMS) Washington DC 2017
    WMSCesium.Ion.defaultAccessToken="token";   constviewer=newCesium.Viewer("cesiumContainer");   //AddaWMSimagerylayer   constlayer=newCesium.ImageryLayer(    newCesium.WebMapServiceImageryProvider({ ......
  • Python基础(四)爬虫
    python爬虫系列文章网上搜素的系列文章记录一下后续可能会用https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzI3NzI1MzY4Mw==&action=getalbum&album_id=1786298272630816773#wechat_redirect1.requests请求2.User-Agent伪装from selenium import webdriverheader......
  • fFFmpeg 命令、案例、测试集中营
    gitbook:https://www.gitbook.com/book/xdsnet/other-doc-cn-ffmpeg/detailsffmpeg[全局选项]{[输入文件选项]-i输入文件}...{[输出文件选项]输出文件}...ffmpeg[global_options]{[input_file_options]-iinput_file}...{[output_file_options]output_file}.........
  • python-爬虫-css提取-写入csv-爬取猫眼电影榜单
    猫眼有一个电影榜单top100,我们将他的榜单电影数据(电影名、主演、上映时间、豆瓣评分)抓下来保存到本地的excle中本案例使用css方式提取页面数据,所以会用到以下库importtimeimportrequestsimportparsel#解析库,解析cssimportcsv#爬取的数据写入csv创建csv文件标头信息......
  • Cesium 案例(二)Web MapTile Service with Time
    使用官方github包,部分解释来源于http://cesium.xin/cesium/cn/Documentation1.95/index.html  Cesium.Ion.defaultAccessToken=token;   constviewer=newCesium.Viewer("cesiumContainer",{    shouldAnimate:true,    //时钟应该默认尝试......
  • Docker制作一个镜像完整过程
    前言以制作CentOS镜像为例,讲述对镜像自定义,打包以及推送的远程仓库的过程,步骤都比较简单可以快速上手。创建步骤创建CentOS基础镜像创建构建目录和Dockerfile,在Dockerfile中编辑镜像相关设置,参考菜鸟教程-Dockerfile。echo"在当前用户目录下创建创建目录docker/build/cent......
  • 案例(一) Z-Indexing Geometry
     使用官方github包,部分解释来源于Viewer-CesiumDocumentation   Cesium.Ion.defaultAccessToken=    "token";   constviewer=newCesium.Viewer("cesiumContainer");   viewer.entities.add({    //entities获取实体集合  ......
  • Scrapy爬虫框架 -- Mysql数据库存储数据
    一、新建一个项目scrapystartprojectmyslqst二、进入到项目目录并增加爬虫文件xiaohuacd.\myslqst\scrapygenspiderxiaohuawww.xxx.com三、修改配置文件,并开启管道存储ROBOTSTXT_OBEY=FalseLOG_LEVEL='ERROR'USER_AGENT="Mozilla/5.0(WindowsNT10.0;Win64;x64)......
  • 在Linux部署单机Redis完整过程
    前言本文以redis-6.2.5为例,讲述在Linux系统下单机Redis下载、编译、打包、安装为服务、启动和访问的整个过程。安装环境为LinuxCentOS64,本文用的7.5版本.下载编译目录在当前目录soft/redis下,最终服务可执行程序在/opt/soft/redis6中。准备工作#查看是否有文件下载......
  • 自学Python爬虫笔记(day2)
    环境python3.9版本及以上,开发工具pycharm 浏览器工具推荐使用Chrome,其他浏览器均有一定程度的阉割。主要是熟练使用后端界面进行爬取数据。 http协议:超文本传输协议把一条消息分为三大块内容:请求:1.请求行-->请求方式(get/post)请求URL地址协议2.请求头-......