「2022年」崔庆才Python3爬虫教程-基础案例爬取实战

在前面我们已经学习了requests、正则表达式的基本用法,但我们还没有完整地实现一个爬取案例,这一节,我们就来实现一个完整的网站爬虫,把前面学习的知识点串联起来,同时加深对这些知识点的理解。1.准备

在前面我们已经学习了requests、正则表达式的基本用法,但我们还没有完整地实现一个爬取案例,这一节,我们就来实现一个完整的网站爬虫,把前面学习的知识点串联起来,同时加深对这些知识点的理解。1.准备工作在本节开始之前,我们需

在前面我们已经学习了 requests、正则表达式的基本用法,但我们还没有完整地实现一个爬取案例,这一节,我们就来实现一个完整的网站爬虫,把前面学习的知识点串联起来,同时加深对这些知识点的理解。

1. 准备工作

在本节开始之前,我们需要做好如下的准备工作:

安装好 Python3,最低为 3.6 版本,并能成功运行 Python3 程序。了解 Python HTTP 请求库 requests 的基本用法。了解正则表达式的用法和 Python 中正则表达式库 re 的基本用法。

以上内容在前面的章节中均有讲解,如尚未准备好建议先熟悉一下这些内容。

2. 爬取目标

本节我们以一个基本的静态网站作为案例进行爬取,需要爬取的链接为 https://ssr1.scrape.center/,这个网站里面包含了一些电影信息,界面如下:

这里首页展示了一个个电影的列表,每部电影包含了它的封面、名称、分类、上映时间、评分等内容,同时列表页还支持翻页,点击相应的页码我们就能进入到对应的新列表页。

如果我们点开其中一部电影,会进入到电影的详情页面,比如我们把第一个电影《霸王别姬》打开,会得到如下的页面:

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

我们本节要完成的目标是:

用 requests 爬取这个站点每一页的电影列表,顺着列表再爬取每个电影的详情页。用 pyquery 和正则表达式提取每部电影的名称、封面、类别、上映时间、评分、剧情简介等内容。把以上爬取的内容存入 MongoDB 数据库。使用多进程实现爬取的加速。

好,那我们现在就开始吧。

3. 爬取列表页

好,第一步的爬取我们肯定要从列表页入手,我们首先观察一下列表页的结构和翻页规则。在浏览器中访问 https://ssr1.scrape.center/,然后打开浏览器开发者工具,我们观察每一个电影信息区块对应的 HTML 以及进入到详情页的 URL 是怎样的,如图所示:

可以看到每部电影对应的区块都是一个 div 节点,它的 class 属性都有 el-card 这个值。每个列表页有 10 个这样的 div 节点,也就对应着 10 部电影的信息。

好,我们再分析下从列表页是怎么进入到详情页的,我们选中电影的名称,看下结果:

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

好的,那接下来我们来分析下翻页的逻辑,我们拉到页面的最下方,可以看到分页页码,如图所示:

这里观察到一共有 100 条数据,10 页的内容,因此页码最多是 10。

接着我们点击第二页,如图所示:

可以看到网页的 URL 变成了 https://ssr1.scrape.center/page/2,相比根 URL 多了 /page/2 这部分内容。网页的结构还是和原来一模一样,可以和第一页一样处理。

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

好,分析到这里,逻辑基本就清晰了。

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

遍历页码构造 10 页的索引页 URL。从每个索引页分析提取出每个电影的详情页 URL。

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

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

import requestsimport loggingimport refrom urllib.parse import urljoinlogging.basicConfig(level=logging.INFO, format=\\\’%(asctime)s – %(levelname)s: %(message)s\\\’)BASE_URL = \\\’https://ssr1.scrape.center\\\’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 代码了。

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

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=\\\”(.*?)\\\”.*?>\\\’) items = re.findall(pattern, html) if not items: return [] for item in items: detail_url = urljoin(BASE_URL, item) logging.info(\\\’get detail url %s\\\’, detail_url) yield detail_url

在这里我们定义了 parse_index 方法,它接收一个 html 参数,即列表页的 HTML 代码。

在 parse_index 方法里面,我们首先定义了一个提取标题超链接 href 属性的正则表达式,内容为:

<a.*?href=\\\”(.*?)\\\”.*?>

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

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

如果 items 为空,那么我们可以直接返回空的列表,如果 items 不为空,那么我们直接遍历处理即可。

遍历 items 得到的 item 就是我们在上文所说的类似 /detail/1 这样的结果。由于这并不是一个完整的 URL,所以我们需要借助 urljoin 方法把 BASE_URL 和 href 拼接起来,获得详情页的完整 URL,得到的结果就类似 https://ssr1.scrape.center/detail/1 这样的完整的 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) logging.info(\\\’detail urls %s\\\’, list(detail_urls))if __name__ == \\\’__main__\\\’: main()

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

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

2020-03-08 22:39:50,505 – INFO: scraping https://ssr1.scrape.center/page/1…2020-03-08 22:39:51,949 – INFO: get detail url https://ssr1.scrape.center/detail/12020-03-08 22:39:51,950 – INFO: get detail url https://ssr1.scrape.center/detail/22020-03-08 22:39:51,950 – INFO: get detail url https://ssr1.scrape.center/detail/32020-03-08 22:39:51,950 – INFO: get detail url https://ssr1.scrape.center/detail/42020-03-08 22:39:51,950 – INFO: get detail url https://ssr1.scrape.center/detail/52020-03-08 22:39:51,950 – INFO: get detail url https://ssr1.scrape.center/detail/62020-03-08 22:39:51,950 – INFO: get detail url https://ssr1.scrape.center/detail/72020-03-08 22:39:51,950 – INFO: get detail url https://ssr1.scrape.center/detail/82020-03-08 22:39:51,950 – INFO: get detail url https://ssr1.scrape.center/detail/92020-03-08 22:39:51,950 – INFO: get detail url https://ssr1.scrape.center/detail/102020-03-08 22:39:51,951 – INFO: detail urls [\\\’https://ssr1.scrape.center/detail/1\\\’, \\\’https://ssr1.scrape.center/detail/2\\\’, \\\’https://ssr1.scrape.center/detail/3\\\’, \\\’https://ssr1.scrape.center/detail/4\\\’, \\\’https://ssr1.scrape.center/detail/5\\\’, \\\’https://ssr1.scrape.center/detail/6\\\’, \\\’https://ssr1.scrape.center/detail/7\\\’, \\\’https://ssr1.scrape.center/detail/8\\\’, \\\’https://ssr1.scrape.center/detail/9\\\’, \\\’https://ssr1.scrape.center/detail/10\\\’]2020-03-08 22:39:51,951 – INFO: scraping https://ssr1.scrape.center/page/2…2020-03-08 22:39:52,842 – INFO: get detail url https://ssr1.scrape.center/detail/112020-03-08 22:39:52,842 – INFO: get detail url https://ssr1.scrape.center/detail/12…

由于输出内容比较多,这里只贴了一部分。

可以看到,这里程序首先爬取了第一页列表页,然后得到了对应详情页的每个 URL,接着再接着爬第二页、第三页,一直到第十页,依次输出了每一页的详情页 URL。这样,我们就成功获取到了所有电影的详情页 URL 啦。

4. 爬取详情页

现在我们已经可以成功获取所有详情页 URL 了,那么下一步当然就是解析详情页并提取出我们想要的信息了。

我们首先观察一下详情页的 HTML 代码吧,如图所示:

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

封面:是一个 img 节点,其 class 属性为 cover。名称:是一个 h2 节点,其内容便是名称。类别:是 span 节点,其内容便是类别内容,其外侧是 button 节点,再外侧则是 class 为 categories 的 div 节点。上映时间:是 span 节点,其内容包含了上映时间,其外侧是包含了 class 为 info 的 div 节点。另外提取结果中还多了「上映」二字,我们可以用正则表达式把日期提取出来。评分:是一个 p 节点,其内容便是评分,p 节点的 class 属性为 score。剧情简介:是一个 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(\\\'(.*?)\\\”.*?>\\\’, 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 标签的中间使用 (.*?) 表示即可。由于结果有多个,所以这里使用 findall 方法提取,结果是一个列表。published_at:上映时间,由于每个上映时间信息都包含了「上映」二字,另外日期又都是一个规整的格式,所以对于这个上映时间的提取,我们直接使用标准年月日的正则表达式 (\\\\d{4}-\\\\d{2}-\\\\d{2}) 表示即可。由于结果只有一个,直接使用 search 方法提取即可。drama:直接提取 class 为 drama 的节点内部的 p 节点的文本即可,同样用 search 方法可以提取。score:直接提取 class 为 score 的 p 节点的文本即可,但由于提取结果是字符串,所以我们还需要把它转成浮点数,即 float 类型。

最后,上述的字段提取完毕之后,构造一个字典返回即可。

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

最后,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) logging.info(\\\’get detail data %s\\\’, data)

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

运行结果如下:

2020-03-08 23:37:35,936 – INFO: scraping https://ssr1.scrape.center/page/1…2020-03-08 23:37:36,833 – INFO: get detail url https://ssr1.scrape.center/detail/12020-03-08 23:37:36,833 – INFO: scraping https://ssr1.scrape.center/detail/1…2020-03-08 23:37:39,985 – INFO: get detail data {\\\’cover\\\’: \\\’https://img.fajihao.com/article/2022/09/17/1490.jpg@464w_644h_1e_1c\\\’, \\\’name\\\’: \\\’霸王别姬 – Farewell My Concubine\\\’, \\\’categories\\\’: [\\\’剧情\\\’, \\\’爱情\\\’], \\\’published_at\\\’: \\\’1993-07-26\\\’, \\\’drama\\\’: \\\’影片借一出《霸王别姬》的京戏,牵扯出三个人之间一段随时代风云变幻的爱恨情仇。段小楼(张丰毅 饰)与程蝶衣(张国荣 饰)是一对打小一起长大的师兄弟,两人一个演生,一个饰旦,一向配合天衣无缝,尤其一出《霸王别姬》,更是誉满京城,为此,两人约定合演一辈子《霸王别姬》。但两人对戏剧与人生关系的理解有本质不同,段小楼深知戏非人生,程蝶衣则是人戏不分。段小楼在认为该成家立业之时迎娶了名妓菊仙(巩俐 饰),致使程蝶衣认定菊仙是可耻的第三者,使段小楼做了叛徒,自此,三人围绕一出《霸王别姬》生出的爱恨情仇战开始随着时代风云的变迁不断升级,终酿成悲剧。\\\’, \\\’score\\\’: 9.5}2020-03-08 23:37:39,985 – INFO: get detail url https://ssr1.scrape.center/detail/22020-03-08 23:37:39,985 – INFO: scraping https://ssr1.scrape.center/detail/2…2020-03-08 23:37:41,061 – INFO: get detail data {\\\’cover\\\’: \\\’https://img.fajihao.com/article/2022/09/17/1491.jpg@464w_644h_1e_1c\\\’, \\\’name\\\’: \\\’这个杀手不太冷 – Léon\\\’, \\\’categories\\\’: [\\\’剧情\\\’, \\\’动作\\\’, \\\’犯罪\\\’], \\\’published_at\\\’: \\\’1994-09-14\\\’, \\\’drama\\\’: \\\’里昂(让·雷诺 饰)是名孤独的职业杀手,受人雇佣。一天,邻居家小姑娘马蒂尔德(纳塔丽·波特曼 饰)敲开他的房门,要求在他那里暂避杀身之祸。原来邻居家的主人是警方缉毒组的眼线,只因贪污了一小包毒品而遭恶警(加里·奥德曼 饰)杀害全家的惩罚。马蒂尔德 得到里昂的留救,幸免于难,并留在里昂那里。里昂教小女孩使枪,她教里昂法文,两人关系日趋亲密,相处融洽。 女孩想着去报仇,反倒被抓,里昂及时赶到,将女孩救回。混杂着哀怨情仇的正邪之战渐次升级,更大的冲突在所难免……\\\’, \\\’score\\\’: 9.5}2020-03-08 23:37:41,062 – INFO: get detail url https://ssr1.scrape.center/detail/3…

由于内容较多,这里省略了后续内容。

可以看到,这里我们就成功提取出来了每部电影的基本信息了,包括封面、名称、类别等等。

5. 保存数据

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

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

import jsonfrom os import makedirsfrom os.path import existsRESULTS_DIR = \\\’results\\\’exists(RESULTS_DIR) or makedirs(RESULTS_DIR)def save_data(data): name = data.get(\\\’name\\\’) data_path = f\\\'{RESULTS_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) logging.info(\\\’get detail data %s\\\’, data) logging.info(\\\’saving data to json file\\\’) save_data(data) logging.info(\\\’data saved successfully\\\’)

这里就是加了 save_data 方法的调用,并加了一些日志信息。

重新运行,我们看下输出结果:

2020-03-09 01:10:27,094 – INFO: scraping https://ssr1.scrape.center/page/1…2020-03-09 01:10:28,019 – INFO: get detail url https://ssr1.scrape.center/detail/12020-03-09 01:10:28,019 – INFO: scraping https://ssr1.scrape.center/detail/1…2020-03-09 01:10:29,183 – INFO: get detail data {\\\’cover\\\’: \\\’https://img.fajihao.com/article/2022/09/17/1490.jpg@464w_644h_1e_1c\\\’, \\\’name\\\’: \\\’霸王别姬 – Farewell My Concubine\\\’, \\\’categories\\\’: [\\\’剧情\\\’, \\\’爱情\\\’], \\\’published_at\\\’: \\\’1993-07-26\\\’, \\\’drama\\\’: \\\’影片借一出《霸王别姬》的京戏,牵扯出三个人之间一段随时代风云变幻的爱恨情仇。段小楼(张丰毅 饰)与程蝶衣(张国荣 饰)是一对打小一起长大的师兄弟,两人一个演生,一个饰旦,一向配合天衣无缝,尤其一出《霸王别姬》,更是誉满京城,为此,两人约定合演一辈子《霸王别姬》。但两人对戏剧与人生关系的理解有本质不同,段小楼深知戏非人生,程蝶衣则是人戏不分。段小楼在认为该成家立业之时迎娶了名妓菊仙(巩俐 饰),致使程蝶衣认定菊仙是可耻的第三者,使段小楼做了叛徒,自此,三人围绕一出《霸王别姬》生出的爱恨情仇战开始随着时代风云的变迁不断升级,终酿成悲剧。\\\’, \\\’score\\\’: 9.5}2020-03-09 01:10:29,183 – INFO: saving data to json file2020-03-09 01:10:29,288 – INFO: data saved successfully2020-03-09 01:10:29,288 – INFO: get detail url https://ssr1.scrape.center/detail/22020-03-09 01:10:29,288 – INFO: scraping https://ssr1.scrape.center/detail/2…2020-03-09 01:10:30,250 – INFO: get detail data {\\\’cover\\\’: \\\’https://img.fajihao.com/article/2022/09/17/1491.jpg@464w_644h_1e_1c\\\’, \\\’name\\\’: \\\’这个杀手不太冷 – Léon\\\’, \\\’categories\\\’: [\\\’剧情\\\’, \\\’动作\\\’, \\\’犯罪\\\’], \\\’published_at\\\’: \\\’1994-09-14\\\’, \\\’drama\\\’: \\\’里昂(让·雷诺 饰)是名孤独的职业杀手,受人雇佣。一天,邻居家小姑娘马蒂尔德(纳塔丽·波特曼 饰)敲开他的房门,要求在他那里暂避杀身之祸。原来邻居家的主人是警方缉毒组的眼线,只因贪污了一小包毒品而遭恶警(加里·奥德曼 饰)杀害全家的惩罚。马蒂尔德 得到里昂的留救,幸免于难,并留在里昂那里。里昂教小女孩使枪,她教里昂法文,两人关系日趋亲密,相处融洽。 女孩想着去报仇,反倒被抓,里昂及时赶到,将女孩救回。混杂着哀怨情仇的正邪之战渐次升级,更大的冲突在所难免……\\\’, \\\’score\\\’: 9.5}2020-03-09 01:10:30,250 – INFO: saving data to json file2020-03-09 01:10:30,253 – INFO: data saved successfully…

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

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

6. 多进程加速

由于整个的爬取是单进程的,而且只能逐条爬取,速度稍微有点慢,我们有没有方法来对整个爬取过程进行加速呢?

在前面我们讲了多进程的基本原理和使用方法,下面我们就来实践一下多进程的爬取吧。

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

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

import multiprocessingdef main(page): 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) logging.info(\\\’get detail data %s\\\’, data) logging.info(\\\’saving data to json data\\\’) save_data(data) logging.info(\\\’data saved successfully\\\’)if __name__ == \\\’__main__\\\’: pool = multiprocessing.Pool() pages = range(1, TOTAL_PAGE + 1) pool.map(main, pages) pool.close() pool.join()

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

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

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

7. 总结

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

我们本节用到的库有 requests、multiprocessing、re、logging 等,通过这个案例实战,我们把前面学习到的知识都串联了起来,其中的一些实现方法可以好好思考和体会,也希望这个案例能够让你对爬虫的实现有更实际的了解。

本节代码:https://github.com/Python3WebSpider/ScrapeSsr1。

本文内容由互联网用户自发贡献,该文观点仅代表作者本人。如发现本站有涉嫌抄袭侵权/违法违规的内容,请发送邮件至 449@qq.com 举报,一经查实,本站将立刻删除。本文链接:https://www.hnhgjc.com/n/128277.html

(0)
发迹号的头像发迹号
上一篇 2022-09-17
下一篇 2022-09-17

相关推荐

  • 荷尔蒙是什么味道(谁会闻到男生荷尔蒙的味道)

    男人的魅力,除了英俊的外表,其实更多的时候,是依靠荷尔蒙来说话的。正如有些刺激的美国大片,居中的男一号不仅有发达的肌肉,更多的时候是荷尔蒙俘获了无数少女心。人类自诞生以后,对于异性的依赖和追求,其实除

    2022-10-01
  • 微信精准加人的3种方法?(微信精准加人方法有哪些)

    随着互联网的飞速发展,一些社交软件的作用也越来越大,很多用户想要使用这类软件进行营销,需要添加大量的用户为好友,但又不知道该如何操作,本期文章就说说“加人又快又多”的方法。一、手机

    2022-06-27
  • 11人场地标准尺寸(建造一个标准场需要多少钱)

    11人场地标准尺寸,建造一个标准场需要多少钱。长行号带你了解更多相关信息。11人制标准人造草坪场地的宽为68m,长为105m,通常情况下11人制场地的长度控制在120-90m之间即可。11人场地标准尺寸球门:球门的规格为7.23*2.44m,浩然体

    2022-08-03
  • 公积金基数越大越好吗(公积金缴存基数是什么意思)

    公积金缴存基数是什么意思?公积金基数越大越好吗,长行号带你了解相关信息。房子、车子、孩子是很多人茶余饭后聊天的资本,对于很多家庭来说,买房不是一件容易的事情,但是又不得不买房,不买房就只能一直租房住。如果在买房时使用公积金贷款的话,可以给购房者节约一大笔费用,但是在此之前需要缴纳住房公积金,缴纳公积金之前需要了解清楚什么是公积金基数。什么是公积金基数?公积金基数一般又可称为公积金缴存基

    2022-06-15
  • 抖音播放量怎么提升(抖音如何不花钱增加浏览量)

    抖音播放量怎么提升,抖音如何不花钱增加浏览量。长行号带你了解更多相关信息。很多人在运营抖音的时候,都会遇到这样的问题,那就是作品没有播放量。就算偶尔有个一两千播放的作品,也是运气好碰到了,所以今天就来好好讲讲抖音想要提升播放量应该怎么做,看了这篇就

    2022-05-02

联系我们

qq:65401449

在线咨询: QQ交谈

邮件:65401449@qq.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信