Бой с рептилиями - научим карабкаться по фильмам Дубана

рептилия

Ставьте лайк и смотрите снова, вырабатывайте хорошую привычку

Python версии 3.8.0, средства разработки: Pycharm


слова, написанные впереди

К настоящему времени вы должны были понять три основных подраздела сканеров:

Если у вас все еще есть проблемы с тремя вышеуказанными подразделами, вы можете点回去再复习一下. В качестве базового контента не требуется, чтобы все его освоили, особенно в третьем разделе, способов парсинга веб-страниц много, и обычным людям сложно запомнить их все.


Когда я это пишу, я буду время от времени возвращаться и просматривать предыдущие статьи.Может быть какие-то способы не самые простые, но пока达成目的хорошо, вот вам, ребята自由发挥.

«Младший брат, почему ты используешь здесь метод find для синтаксического анализа, могу ли я использовать регулярные выражения?»
«Конечно, можно, может быть, ваше регулярное выражение проще реализовать»


Что ж, в качестве первого небольшого проекта нашего краулера я постараюсь изо всех сил讲清楚每一步代码, даже если не упоминается,会有注释, не беспокойтесь о том, что не сможете идти в ногу.


Кроме того, хоть это и первая статья о краулерах, я все же проведу анализ данных по результатам работы краулеров. Это относительно просто для проекта, цель состоит в том, чтобы каждый了解整个分析的过程.


Помните одно: краулер никогда не является нашей целью, в лучшем случае это путь нашего анализа данных.踏板.

源码获取方式在文末



текст

четкие потребности
Данные, которые мы собираемся сканировать сегодня,豆瓣电影Top250, да, всего 250 штук данных, вы правильно догадались.
Введите URLhttps://movie.douban.com/top250Мы видим, что страница выглядит так:

250条数据Ясно, без проблем.

Как видите, на этой странице фактически уже есть видео主要内容: Название фильма, последовательность, сценарист, главная роль, год, жанр, количество комментариев, рейтинг, в основном все на этой странице.
но я нажимаю详细影片После этого нашел это:


Вроде данные на этой странице более полные.Что мы хотим сканировать данные,чем больше данных тем лучше. По сравнению с этим подробным содержанием, обзоров фильмов с каждой звездной оценкой больше, поэтому мы должны выбрать его.


Хорошо, давайте разберемся с нашими мыслями

  • Прежде всего, войдите в Douban Movie Top250, всего 10 страниц, 25 фильмов на странице.
  • Затем для каждой страницы с 25 видео перейдите на страницу с подробным содержанием.
  • Наконец, проанализируйте подробный контент каждого фильма и сохраните контент в базе данных.


    писать псевдокод
# 遍历10页
data_movies # 保存所有影片数据集
for per_page in pages:    
    # 爬取10页的每一页数据 
    movies = craw_page_info(per_page)
    # 遍历每一页的25个影片
    for movie in movies:
        # 爬取每个影片的详细内容
        data_per_movie = craw_detail_info(movie)
        # 保存每个影片信息到数据集中
        data_movies.append(data_per_movie)

# 保存结果到数据库中
data_movies_to_mysql

Чтобы немного объяснить:两层循环, первый слой должен пройти10页网页, так как на каждой веб-странице 25 видео, поэтому цикл второго слоя проходит по очереди25个影片Получите подробную информацию и, наконец, сохраните результаты в базу данных!
Разве это не просто!

Однако на практике вы можете столкнуться со всевозможными проблемами, так что будьте морально готовы!


Начать

Во-первых, определите поле фильма, которое мы хотим вывести.
主要数据В том числе: порядок фильмов, название фильма, режиссер фильма, сценарист фильма, кинозвезда, название фильма, ссылка на фильм
关键数据В том числе: тип фильма, страна производства, язык фильма, дата выхода, продолжительность фильма.
核心数据В том числе: рейтинг видео, количество комментариев, доля комментариев, соответствующих каждой звездочке рейтинга 5/4/3/2/1

字段如下:

movie_rank:影片排序
movie_name:影片名称
movie_director:影片导演
movie_writer:影片编剧
movie_starring:影片主演
movie_type:影片类型
movie_country:影片制片国家
movie_language:影片语言
movie_release_date:影片上映日期
movie_run_time:影片片长
movie_second_name:影片又名
movie_imdb_href:影片IMDb 链接
movie_rating:影片总评分
movie_comments_user:影片评论人数
movie_five_star_ratio:影片5星占比
movie_four_star_ratio:影片4星占比
movie_three_star_ratio:影片3星占比
movie_two_star_ratio:影片2星占比
movie_one_star_ratio:影片1星占比
movie_note:影片备注信息,一般为空


Затем запустите основной процесс
Подтвердите основные параметры, номер начальной страницы (по умолчанию 0), 25 видео на странице, всего 10 страниц,
参数如下:

start_page:起始页码
page_size:每一页大小
pages:总页码


определить объект класса

Здесь мы инкапсулируем каждый фильм в объект, передаем наши основные параметры, устанавливаем голову сканера и устанавливаем соединение с базой данных.

类定义对象如下:

class DouBanMovie:
    def __init__(self, url, start_page, pages, page_size):
        """
        初始化
        @param url: 爬取主网址
        @param start_page: 起始页码
        @param pages: 总页码(截止页码)
        @param page_size: 每页的大小
        """
        self.url = url
        self.start_page = start_page
        self.pages = pages
        self.page_size = page_size
        self.data_info = []
        self.pymysql_engine, self.pymysql_session = connection_to_mysql()
        self.headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36'
        }

«Младший брат, что ты используешь для соединения с базой данных, почему я не могу этого понять?»
«Я инкапсулировал его, и для подключения к базе данных используется SQLAlchemy».

Не волнуйтесь, в будущем я напишу специальные операции, связанные с базой данных SQLAlchemy.

#  创建基类,
Base = declarative_base()

def connection_to_mysql():
    """
    连接数据库
    @return:
    """
    engine = create_engine('mysql+pymysql://username:passwd@localhost:3306/db_name?charset=utf8')
    Session = sessionmaker(bind=engine)
    db_session = Session()
    # 创建数据表
    Base.metadata.create_all(engine)

    return engine, db_session


Определяем основной кадр:

# 如果当前页码小于0,异常退出
if self.start_page < 0:
    return ""
# 如果起始页面大于总页码数,退出
if self.start_page > self.pages:
    return ""

# 若当前页其实页码小于总页数,继续爬取数据
while self.start_page < pages:
    # 拼接当前页的网址
    # 主爬虫代码
    # 下一页
    self.start_page = self.start_page + 1

拼接当前页的网址Объясните здесь, когда мы посещаем первую страницу, мы обнаруживаем, что URL-адрес выглядит следующим образом

https://movie.douban.com/top250

Когда я захожу на следующую страницу, я обнаруживаю, что URL-адрес меняется следующим образом.

https://movie.douban.com/top250?start=25&filter=

URL-адрес на следующей странице изменится следующим образом:

https://movie.douban.com/top250?start=50&filter=

Как видите, новый URL просто变化了后面的 start 参数, поэтому мы разделили URL каждой страницы:

start_number = self.start_page * self.page_size
new_url = self.url + '?start=' + str(start_number) + '&filter='



Просканировать первую страницу
После определения основного кадра нам нужно просканировать第一个网页, то есть содержит25个影片的页面.
В настоящее время метод реализации краулера, упомянутый в первых трех разделах, напрямую перенимается:

self.headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36',
}

# 爬取当前页码的数据
response = requests.get(url=new_url, headers=self.headers)

После успешного получения данных страницы нам нужно对页面解析, получить каждое видео跳转详细页面的超链接

Исходный код веб-страницы можно просмотреть с помощью инструментов разработчика Google Chrome F12.

Вы можете увидеть детали каждого видео вli 标签, а в каждом теге li естьclass='pic' 的 div, внутри div есть такойa 标签середина

И href этого тега - это именно то, что нам нужно.详细页面信息的超链接


Определите местоположение гиперссылки, откройте подробное объяснение BeautifulSoup в предыдущем разделе, найдите и проанализируйте

soup = BeautifulSoup(response.text, 'html.parser')
# 定位到每一个电影的 div (pic 标记的 div)
soup_div_list = soup.find_all(class_="pic")
# 遍历获取每一个 div 的电影详情链接
for soup_div in soup_div_list:
    # 定位到每一个电影的 a 标签
    soup_a = soup_div.find_all('a')[0]
    movie_href = soup_a.get('href')
    print(movie_href)

Получите гиперссылки на детали 25 видео на текущей странице

我们离成功又进了一步!


Сканирование страниц с подробными сведениями
Точно так же одна строка кода удаляет данные страницы

'''爬取页面,获得详细数据'''
response = requests.get(url=movie_detail_href, headers=self.headers)

Создайте упорядоченный словарь для хранения текущих данных фильма

# 生成一个有序字典,保存影片结果
movie_info = OrderedDict()

Давайте посмотрим, как выглядит исходный код этой страницы.Во-первых, это порядок и название фильма, которые мы можем передать с предыдущей страницы. Но так как он здесь, могу ли я просто разобрать его напрямую?

Должен сделать!

Это проще, рейтинг видео напрямую определяет местонахождение одногоclass='top250-no' 的 span 标签, название видео нацелено наproperty='v:itemreviewed' 的 span 标签, получить содержимое ярлыка

# 解析电影排名和名称
movie_info['movie_rank'] = soup.find_all('span', class_='top250-no')[0].string
movie_info['movie_name'] = soup.find_all('span', property='v:itemreviewed')[0].string



Далее идут основные данные видео:

В этот момент нам нужно найтиid='info' 的 div, тогда вы можете увидеть整个 div 的数据Это основные данные, которые нам нужны.

# 定位到影片数据的 div
soup_div = soup.find(id='info')

«Нет, братишка, я обнаружил, что иногда есть один сценарист, иногда их несколько. Когда их несколько, они существуют в нескольких тегах span. Что мне делать?»

«Это просто, я напишу небольшую функцию и единообразно ее обработаю».

def get_mul_tag_info(self, soup_span):
    """
    获取多个标签的结果合并在一个结果中返回,并用 / 分割
    """
    info = ''
    for second_span in soup_span:
        # 区分 href 和标签内容
        info = ('' if (info == '') else '/').join((info, second_span.string))

    return info

«Кстати, не забудьте дать мне самый внешний тег span. Вот так:»

# 解析电影发布信息
movie_info['movie_director'] = self.get_mul_tag_info(soup_div.find_all('span')[0].find_all('a'))
movie_info['movie_writer'] = self.get_mul_tag_info(soup_div.find_all('span')[3].find_all('a'))
movie_info['movie_starring'] = self.get_mul_tag_info(soup_div.find_all('span')[6].find_all('a'))
movie_info['movie_type'] = self.get_mul_tag_info(soup_div.find_all('span', property='v:genre'))
movie_info['movie_country'] = soup_div.find(text='制片国家/地区:').next_element.lstrip().rstrip()
movie_info['movie_language'] = soup_div.find(text='语言:').next_element.lstrip().rstrip()
movie_info['movie_release_date'] = self.get_mul_tag_info(soup_div.find_all('span', property='v:initialReleaseDate'))
movie_info['movie_run_time'] = self.get_mul_tag_info(soup_div.find_all('span', property='v:runtime'))
movie_info['movie_imdb_href'] = soup_div.find('a', target='_blank')['href']

"Братишка, опять проблема, некоторые видео не又名ярлык, как с этим бороться? "
«Мы делаем для этого обнаружение аномалий, и если их нет, мы можем вручную присвоить нулевое значение».

movie_second_name = ''
try:
    movie_second_name = soup_div.find(text='又名:').next_element.lstrip().rstrip()
except AttributeError:
    print('{0} 没有又名'.format(movie_info['movie_name']))
    movie_info['movie_second_name'] = movie_second_name

Окончательный счет остается
Данные рейтинга включают не только общий рейтинг, но и рейтинг каждой звезды.

«Младший брат, как ты думаешь, какие данные мы возьмем?»
"Дети делают выбор, конечно, я хочу их всех!"



Как видите, есть один общий рейтинг и одно общее количество комментариев.唯一的 property, соответственноproperty='v:average' 的 strong 标签иproperty='v:votes' 的 span 标签

хорошо, тогда получите данные напрямую:

# 获取总评分和总评价人数
movie_info['movie_rating'] = soup.find_all('strong', property='v:average')[0].string
movie_info['movie_comments_user'] = soup.find_all('span', property='v:votes')[0].string



наконец ушел每个星级的评分占比, можно увидеть5星/4星/3星/2星/1星соответствуют соответственно力荐/推荐/还行/较差/很差, вы можете видеть, что все они существуют вclass='ratings-on-weight' 的 divсередина

Итак, сначала поместите div:

# 定位到影片星级评分占比的 div
soup_div = soup.find('div', class_="ratings-on-weight")

Затем получите процентные данные каждого звездного рейтинга:

# 获取每个星级的评分
movie_info['movie_five_star_ratio'] = soup_div.find_all('div')[0].find(class_='rating_per').string
movie_info['movie_four_star_ratio'] = soup_div.find_all('div')[2].find(class_='rating_per').string
movie_info['movie_three_star_ratio'] = soup_div.find_all('div')[4].find(class_='rating_per').string
movie_info['movie_two_star_ratio'] = soup_div.find_all('div')[6].find(class_='rating_per').string
movie_info['movie_one_star_ratio'] = soup_div.find_all('div')[8].find(class_='rating_per').string

распечатайте и смотрите нас当前的影片данные:

Отображается только выходная часть поля movie_starring

OrderedDict(
    [
        ('movie_rank', 'No.1'), 
        ('movie_name', '肖申克的救赎 The Shawshank Redemption'), 
        ('movie_director', '弗兰克·德拉邦特'), 
        ('movie_writer', '弗兰克·德拉邦特/斯蒂芬·金'), 
        ('movie_starring', '蒂姆·罗宾斯/摩根·弗里曼/鲍勃·冈顿/威廉姆·赛德勒/), 
        ('movie_type', '剧情/犯罪'), 
        ('movie_country', '美国'), 
        ('movie_language', '英语'), 
        ('movie_release_date', '1994-09-10(多伦多电影节)/1994-10-14(美国)'), 
        ('movie_run_time', '142分钟'), 
        ('movie_imdb_href', 'https://www.imdb.com/title/tt0111161'), 
        ('movie_rating', '9.7'), 
        ('movie_comments_user', '1720706'), 
        ('movie_five_star_ratio', '84.8%'), 
        ('movie_four_star_ratio', '13.6%'), 
        ('movie_three_star_ratio', '1.4%'), 
        ('movie_two_star_ratio', '0.1%'), 
        ('movie_one_star_ratio', '0.1%'), 
        ('movie_note', '')
    ]
)

Готово, успешно получили нужные данные, последний шаг:保存数据库

# 保存当前影片信息
self.data_info.append(movie_info)

# 获取数据并保存成 DataFrame
df_data = pd.DataFrame(self.data_info)
# 导入数据到 mysql 中
df_data.to_sql('t_douban_movie_top_250', self.pymysql_engine, index=False, if_exists='append')

Взгляните на нашу базу данных, все данные, которые должны храниться в ней

到这里,爬虫就算是结束了.



в заключении:

Готов к работе:

  • Сначала мы определяем объект фильма, передаем информацию о параметрах URL-адреса, устанавливаем заголовок сканера и устанавливаем соединение с базой данных.
  • Мы проанализировали гиперссылки каждой страницы фильма через следующую страницу и обнаружили, что изменились только параметры.
  • Устанавливается основной процесс и записывается псевдокод основного процесса



Запустите поисковый робот:

  • Ползание第一页веб-контент
  • Разобрать第一页, получить подробные гиперссылки на 25 видео на странице
  • Ползание详细影片веб-контент
  • Разобрать第二页контент, сохраненный для каждого объекта фильма
  • 保存数据в базу данных



считать:
Вышеизложенное является основным содержанием нашего сегодняшнего боя на гусеничном ходу, который относительно прост.
Первый проект направлен на то, чтобы все знали爬虫流程, а также учитывать следующие моменты:

  • Данные краткого комментария для страницы сведений о видео
  • Данные о наградах на странице сведений о фильме
  • Данные доски обсуждений для страницы сведений о видео

Можно ли получить вышеуказанные данные今天的获取方法? Если нет, то как получить эти данные?


слова, написанные на спине

Сегодняшний фактический боевой проект закончен, студенты, которым нужен исходный код, могут перейти к公众号后台Ответить豆瓣电影Получите это, если вы думаете, что речь младшего брата неплохая, вы могли бы также点个赞?

Как упоминалось в начале, наша цель не в том, чтобы сканировать данные. Итак, я буду использовать эти данные для простого анализа данных, цель очень проста: понять процесс анализа данных. Увидимся в следующий раз.

подумай об этом

Я обнаружил, что писать технические статьи более чем на один уровень сложнее, чем писать программные статьи.Хотя в программных статьях мало технического содержания, все любят их читать.

Технические статьи горькие и трудные для понимания, но как бы подробно я ни сказал, вы уверены, что не хотите щелкнуть по ним, чтобы поддержать их?


Нелегко быть оригинальным, пожалуйста, лайкни

Первый выпуск статьи: публичный аккаунт [Чжицю Сяомэн]

Синхронизация статей: Наггетс, Цзяньшу



Отрисовка кода на мобильном телефоне кажется немного неправильной, и выглядит довольно неуклюже.
Но компьютерный дисплей нормальный, что очень странно

Если вы хотите увидеть ссылку для обычного рендеринга, вы можете нажать на этуБой с рептилиями - научим карабкаться по фильмам Дубана