【爬虫】学习:正则、Beautiful Soup、Pyquery

编程入门 行业动态 更新时间:2024-10-23 09:22:04

【<a href=https://www.elefans.com/category/jswz/34/1770264.html style=爬虫】学习:正则、Beautiful Soup、Pyquery"/>

【爬虫】学习:正则、Beautiful Soup、Pyquery

Github

参考:《python3网络爬虫开发实战第二版》——2.5基础爬虫案例实战

正则表达式、request

技巧

看源代码


源代码有和显示的代码有出入

<img data-src=".jpg?imageView2/1/w/160/h/220" alt="我不是药神" class="board-img" />

使用CSS选择器

cover_pattern = repile('<img.*?class="board-img".*?src="(.*?)">')  # 封面

$(’.board-img’)有多个元素,所以上述的正则表达式需要再精确范围

电影网站

正则表达式 + 文本 + 多进程版

import requests
import logging
import re
from urllib.parse import urljoin  # 拼接路径logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s: %(message)s')BASE_URL = ''
TOTAL_PAGE = 10  # 总共有10页def scrape_page(url):"""获取页面html代码:param url: 链接:return: 页面的HTML代码"""logging.info('scraping %s...', url)try:response = requests.get(url)if response.status_code == 200:  # 请求成功return response.textlogging.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)def scrape_index(page):"""构造链接:param page: 列表页的页数:return: 完整的列表页链接"""index_url = f'{BASE_URL}/page/{page}'return scrape_page(index_url)def parse_index(html):"""获取详情页的链接:param html:列表页的html:return: 详情页的url"""pattern = repile('<a.*?href="(.*?)".*?class="name">')  # 获取相对路径 如 /detail/1items = 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_urldef scrape_detail(url):"""获取详情页的html:param url: 详情页的url:return: 详情页的html"""return scrape_page(url)def parse_detail(html):"""解析详情页数据:param html: 详情页的html:return: 数据字典"""# 正则表达式 引号外单内双cover_pattern = repile('class="item.*?<img.*?src="(.*?)".*?class="cover">', re.S)  # 封面name_pattern = repile('<h2.*?>(.*?)</h2>')  # 名称categories_pattern = repile('<button.*?category.*?<span>(.*?)</span>.*?</button>', re.S)  # 需要匹配换行符 且有多个分类published_at_pattern = repile('(\d{4}-\d{2}-\d{2}).*?上映')  # 上映时间drama_pattern = repile('<div.*?class="drama">.*?<p.*?>(.*?)</p></div>', re.S)  # 剧情简介score_pattern = repile('<p.*?score.*?>(.*?)</p>', re.S)  # 评分# 如果没找到就返回Nonecover = re.search(cover_pattern, html).group(1).strip() if re.search(cover_pattern, html) else Nonename = 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 Nonedrama = re.search(drama_pattern, html).group(1).strip() if re.search(drama_pattern, html) else Nonescore = 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}import json
from os import makedirs
from 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)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))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('save data to json file')save_data(data)logging.info('data saved successfully')if __name__ == '__main__':main()

日志

2021-12-24 17:29:03,587 - INFO: get detail url /detail/99
2021-12-24 17:29:03,587 - INFO: scraping /detail/99...
2021-12-24 17:29:04,876 - INFO: get detail data {'cover': '/static/img/logo.png', 'name': '教父2 - The Godfather: Part Ⅱ', 'categories': ['剧情', '犯罪'], 'published_at': '1974-12-12', 'drama': '影片主要讲述第二代教父麦克·柯里昂(阿尔·帕西诺 饰)的奋斗历程,同时回忆了第一代教父维多·柯里昂(罗伯特·德尼罗 饰)创业的艰辛,反映了不同历史时期,两代教父的事业、家庭生活。麦克为儿子托尼举行圣餐仪式和庆祝活动的当夜,麦克在家中遭到袭击,凶手被人灭口,面临接管家族事业以来的重重危机,麦克回忆起了父亲维多·柯里昂年轻时在美国的创业历程。麦克一边调查袭击的真相,一边继续开展赌博、酒店等生意,和另一个黑帮人物海门罗斯斗智斗勇,不断扩大势力。终于,麦克的不法行为引起了政府的关注,麦克受到一系列的指控;同时,麦克的家庭也遇到了危机,夫妻感情濒临破裂;而最让麦克痛心的,却是家族中,亲人的背叛。和第一代教父其乐融融的家庭生活比起来,麦克无疑很失败。麦克怎么样面对事业、家庭的双重危机?为什么两代教父会有截然不同的家庭生活?让我们自己在影片中寻找答案。', 'score': 9.0}
2021-12-24 17:29:04,876 - INFO: save data to json file
2021-12-24 17:29:04,877 - INFO: data saved successfully
2021-12-24 17:29:04,877 - INFO: get detail url /detail/90
2021-12-24 17:29:04,877 - INFO: scraping /detail/90...
2021-12-24 17:29:05,649 - INFO: get detail data {'cover': '/static/img/logo.png', 'name': '辛德勒的名单 - Schindler&#x27;s List', 'categories': ['剧情', '历史', '战争'], 'published_at': '1993-11-30', 'drama': '1939年,波兰在纳粹德国的统治下,党卫军对犹太人进行了隔离统治。德国商人奥斯卡·辛德勒(连姆·尼森 饰)来到德军统治下的克拉科夫,开设了一间搪瓷厂,生产军需用品。凭着出众的社交能力和大量的金钱,辛德勒和德军建立了良好的关系,他的工厂雇用犹太人工作,大发战争财。1943年,克拉科夫的犹太人遭到了惨绝人寰的大屠杀,辛德勒目睹这一切,受到了极大的震撼,他贿赂军官,让自己的工厂成为集中营的附属劳役营,在那些疯狂屠杀的日子里,他的工厂也成为了犹太人的避难所。1944年,德国战败前夕,屠杀犹太人的行动越发疯狂,辛德勒向德军军官开出了1200人的名单,倾家荡产买下了这些犹太人的生命。在那些暗无天日的岁月里,拯救一个人,就是拯救全世界。', 'score': 9.5}
2021-12-24 17:29:05,650 - INFO: save data to json file
2021-12-24 17:29:05,651 - INFO: data saved successfully

多线程

# 多线程
import multiprocessingdef main(page):index_html = scrape_index(page)detail_urls = parse_index(index_html)# logging.info('detail urls %s',list(detail_urls))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('save data to json file')save_data(data)logging.info('data saved successfully')if __name__ == '__main__':pool = multiprocessing.Pool()pages = range(1, TOTAL_PAGE + 1)# map()函数会将第二个参数(可迭代的列表)的元素一个个的传入第一个参数的函数中pool.map(main, pages)  # 每个main开启一个线程pool.close()pool.join()

PyQuery + MongoDB + 多进程版

待定

猫眼top100

注意:这个网站的显示内容与源代码有出入,请看源代码选择元素

正则

参考: 《python3网络爬虫开发实战第一版》——3.4

爬取速度过快会有滑动验证码
只需在浏览器访问该网页手动验证一下,然后就可以继续爬

import requests
import json
import re
import timeclass Spider:def __init__(self):self.url_temp = '={}'self.headers = {'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.87 Safari/537.36'}# 1.根据地址的规律构造url_listdef get_url_list(self):url_list = [self.url_temp.format(i) for i in range(0, 91, 10)]return url_list# 2.发送请求,获取响应def req_url_resp(self, url):print('start:' + url)response = requests.get(url, headers=self.headers)# print(response.text)return response.content.decode()# 3.提取数据def get_content_list(self, resp_content):content_list = []  # 用列表存储信息pattern = repile('<dd>.*?board-index.*?>(\d+)</i>.*?<img.*?data-src="(.*?)".*?name".*?><a.*?>(.*?)</a>.*?star">(.*?)</p>''.*?releasetime">(.*?)</p>.*?integer">(.*?)</i>.*?fraction">(.*?)</i>.*?</dd>', re.S)contents = re.findall(pattern, resp_content)for content in contents:item = {}item["id"] = content[0]item["img_url"] = content[1]item["name"] = content[2]item["star"] = content[3].strip()[3:]item["releasetime"] = content[4][5:]item["grade"] = content[5] + content[6] + "分"# print(id,name,star,releasetime,grade,img_url)content_list.append(item)return content_list# 4.保存数据def save_content(self, content_list):with open("猫眼Top100.txt", 'a', encoding='utf-8') as f:for content in content_list:f.write(json.dumps(content, ensure_ascii=False))f.write('\n')print('保存成功!')def run(self):# 1.根据地址的规律构造url_listurl_list = self.get_url_list()# 2.发送请求,获取响应for url in url_list:time.sleep(1)resp_content = self.req_url_resp(url)# 3.提取数据content_list = self.get_content_list(resp_content)# 4.保存数据self.save_content(content_list)print("爬取结束")if __name__ == '__main__':spider = Spider()spider.run()

结果

{"id": "1", "img_url": ".jpg?imageView2/1/w/160/h/220", "name": "我不是药神", "star": "徐峥,周一围,王传君", "releasetime": "2018-07-05", "grade": "9.6分"}
{"id": "2", "img_url": ".jpg?imageView2/1/w/160/h/220", "name": "肖申克的救赎", "star": "蒂姆·罗宾斯,摩根·弗里曼,鲍勃·冈顿", "releasetime": "1994-09-10(加拿大)", "grade": "9.5分"}
{"id": "3", "img_url": ".jpg?imageView2/1/w/160/h/220", "name": "绿皮书", "star": "维果·莫腾森,马赫沙拉·阿里,琳达·卡德里尼", "releasetime": "2019-03-01", "grade": "9.5分"}
{"id": "4", "img_url": ".jpg?imageView2/1/w/160/h/220", "name": "海上钢琴师", "star": "蒂姆·罗斯,比尔·努恩 ,克兰伦斯·威廉姆斯三世", "releasetime": "2019-11-15", "grade": "9.3分"}
{"id": "5", "img_url": ".jpg?imageView2/1/w/160/h/220", "name": "哪吒之魔童降世", "star": "吕艳婷,囧森瑟夫,瀚墨", "releasetime": "2019-07-26", "grade": "9.6分"}

Beautiful Soup

css选择器(推荐)

select() 返回符合css选择器的节点组成的列表,列表中的元素也是Tag类型

只要是Tag类型,就可以嵌套选择

获取属性 node[‘id’] 或 node.attrs['id] 有s
获取文本 node.string 或 node.get_text()

from bs4 import BeautifulSoup# 3.提取数据def get_content_list(self, resp_content):soup=BeautifulSoup(resp_content,'lxml')content_list = []  # 用列表存储信息# css 选择器for dd in soup.select('.board-wrapper dd'):item = {}# print(type(dd)) # <class 'bs4.element.Tag'> 可以嵌套选择# print(dd.select('.board-index')) # [<i class="board-index board-index-1">1</i>] 是一个列表item["id"] = dd.select('.board-index')[0].get_text() # string也可以# print(dd.select('img')) # 列表有两个元素# print(type(dd.select('img')[1])) # 列表中的元素依然是<class 'bs4.element.Tag'>item["img_url"] = dd.select('img')[1].attrs['data-src'] # dd.select('img')[1]['data-src']也可以item['name']=dd.select('.name')[0].get_text()item["star"] = dd.select('.star')[0].get_text()item["releasetime"] = dd.select('.releasetime')[0].get_text()[5:]item["grade"] = dd.select('.integer')[0].string+dd.select('.fraction')[0].string + "分"# print(id,name,star,releasetime,grade,img_url)content_list.append(item)return content_list

方法选择器

find_all(name,attrs,recursive,text,**kwargs) 返回一个符合条件的节点组成的列表,列表中的元素也是Tag类型

find() 返回第一个匹配的节点

	# 3.提取数据def get_content_list(self, resp_content):soup = BeautifulSoup(resp_content, 'lxml')content_list = []  # 用列表存储信息# 方法选择器# 	print(soup.find_all(attrs={'class': 'board-wrapper'}))dl=soup.find_all(attrs={'class': 'board-wrapper'}) # 返回一个列表for dd in dl[0].find_all(name='dd'):item = {}# print(type(dd))  # <class 'bs4.element.Tag'> 可以嵌套选择# print(dd.find(class_='board-index'))  # <i class="board-index board-index-1">1</i> 是一个节点元素item["id"] = dd.find(class_='board-index').get_text()  # dd.find(attr={'class':'board-index'})也可以# print(dd.find(name='img',class_='board-img'))item["img_url"] = dd.find(name='img',class_='board-img').attrs['data-src']item['name'] = dd.find(class_='name').get_text()item["star"] = dd.find(class_='star').get_text()item["releasetime"] = dd.find(class_='releasetime').get_text()[5:]item["grade"] = dd.find(class_='integer').string + dd.find(class_='fraction').string + "分"content_list.append(item)return content_list

pyquery

和jQuery api一样的 常用
css选择器 伪类选择器 find() parent() remove() 通通都可以用

获取属性
.attr(‘id’) 没有s 返回第一个节点的属性
.attr.id

选择结果可能是多个节点,也可能是单个节点,类型都是PyQuery
多个节点遍历获取请用item()

from pyquery import PyQuery as pq# 3.提取数据def get_content_list(self, resp_content):doc = pq(resp_content)content_list = []  # 用列表存储信息dd_list=doc('.board-wrapper dd').items() # 生成器 变成一个列表 里面的元素都是PyQuery类型print(dd_list) # <generator object PyQuery.items at 0x000001BB98B3AE40> print(type(dd_list)) # <class 'generator'>for dd in dd_list:item = {}print(type(dd))  # <class 'pyquery.pyquery.PyQuery'>print(dd('.board-index'))  # <i class="board-index board-index-1">1</i> 是一个节点元素item["id"] = dd('.board-index').text()item["img_url"] = dd('img .board-img').attr['data-src']item['name'] = dd('.name').text()item["star"] = dd('.star').text()item["releasetime"] = dd('.releasetime').text()[5:]item["grade"] = dd('.integer').text() + dd('.fraction').text() + "分"content_list.append(item)return content_list

更多推荐

【爬虫】学习:正则、Beautiful Soup、Pyquery

本文发布于:2024-03-10 14:52:38,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1728275.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:爬虫   正则   Beautiful   Soup   Pyquery

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!