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

Github

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

正则表达式、request

技巧

看源代码

在这里插入图片描述
源代码有和显示的代码有出入

<img data-src="https://p0.pipi.cn/mmdb/d2dad59253751bd236338fa5bd5a27c710413.jpg?imageView2/1/w/160/h/220" alt="我不是药神" class="board-img" />

在这里插入图片描述

使用CSS选择器

cover_pattern = re.compile('<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 = 'https://ssr1.scrape.center'
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.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)


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 = re.compile('<a.*?href="(.*?)".*?class="name">')  # 获取相对路径 如 /detail/1
    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


def scrape_detail(url):
    """
    获取详情页的html
    :param url: 详情页的url
    :return: 详情页的html
    """
    return scrape_page(url)


def parse_detail(html):
    """
    解析详情页数据
    :param html: 详情页的html
    :return: 数据字典
    """
    # 正则表达式 引号外单内双
    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}).*?上映')  # 上映时间
    drama_pattern = re.compile('<div.*?class="drama">.*?<p.*?>(.*?)</p></div>', re.S)  # 剧情简介
    score_pattern = re.compile('<p.*?score.*?>(.*?)</p>', re.S)  # 评分

    # 如果没找到就返回None
    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
    }


import json
from os import makedirs
from os.path import exists

RESULTS_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 https://ssr1.scrape.center/detail/99
2021-12-24 17:29:03,587 - INFO: scraping https://ssr1.scrape.center/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 https://ssr1.scrape.center/detail/90
2021-12-24 17:29:04,877 - INFO: scraping https://ssr1.scrape.center/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 multiprocessing

def 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 time

class Spider:
    def __init__(self):
        self.url_temp = 'https://maoyan.com/board/4?offset={}'
        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_list
    def 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 = re.compile(
            '<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_list
        url_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": "https://p0.pipi.cn/mmdb/d2dad59253751bd236338fa5bd5a27c710413.jpg?imageView2/1/w/160/h/220", "name": "我不是药神", "star": "徐峥,周一围,王传君", "releasetime": "2018-07-05", "grade": "9.6分"}
{"id": "2", "img_url": "https://p0.pipi.cn/mmdb/d2dad592c7e7e1d236f2aa1811a8a64794b33.jpg?imageView2/1/w/160/h/220", "name": "肖申克的救赎", "star": "蒂姆·罗宾斯,摩根·弗里曼,鲍勃·冈顿", "releasetime": "1994-09-10(加拿大)", "grade": "9.5分"}
{"id": "3", "img_url": "https://p0.pipi.cn/mmdb/d2dad59253751b230f21f0818a5bfd4d8679c.jpg?imageView2/1/w/160/h/220", "name": "绿皮书", "star": "维果·莫腾森,马赫沙拉·阿里,琳达·卡德里尼", "releasetime": "2019-03-01", "grade": "9.5分"}
{"id": "4", "img_url": "https://p0.pipi.cn/mmdb/d2dad592c7e7e1d2365bf1b63cd25951b722b.jpg?imageView2/1/w/160/h/220", "name": "海上钢琴师", "star": "蒂姆·罗斯,比尔·努恩 ,克兰伦斯·威廉姆斯三世", "releasetime": "2019-11-15", "grade": "9.3分"}
{"id": "5", "img_url": "https://p0.pipi.cn/mmdb/d2dad592537923f0ee07acada3ac59b9f3ffb.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
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值