【2】爬虫学习

这篇博客主要是从具体的项目入手,通过项目来学习相关的爬虫技术,以做代学。

分析Ajax请求抓取今日头条街拍美图

前提概念补充

​ 有时候我们在用request抓取页面的时候,得到的结果和浏览器中看到的可能不一样,在浏览器中看到的是正常显示的页面数据,但是在request得到的结果并没有。这是因为获取得到的都是原始的HTML文档,但是浏览器页面是经过 JavaScript处理数据后生成的结果。这些数据可能是通过Ajax加载得到的。

​ 对于Ajax加载数据的情况来说,数据加载是一种异步加载的方式,原始的页面最初不会包含某些数据,原始页面加载完成之后,会再向服务器请求某个接口获取数据。然后数据才会被处理从而呈现在网页上。这其实就是发送了一个Ajax请求。现在网页发展就是这样:网页原始的HTML文档不包含任何数据,数据都是通过Ajax统一加载之后呈现出来的,这样就可以在Web开发商可以做前后端分离,而且降低服务器直接渲染页面带来的压力。

​ 如果遇到这样的页面是无法获取有效数据的,这时就需要分析网页后台向接口发送的Ajax请求,如果可以用requests来模拟Ajax请求,那么就可以成功抓取了。

  • Ajax

在说这个项目之前,一般的爬虫小白都会对这个标题中的Ajax产生疑问需要先解释一下AjaxAjax就是Asynchronous JavaScript and XML(异步的JavaScriptXML技术)说白了就是在不关闭不跳转不刷新的情况下,在网页后台提交数据,部分更新页面的内容。在这个过程中,页面实际上是在后台与服务器进行了数据交互,获取到数据之后,再利用JavaScript改变网页,这样网页内二就会更新了。

举例说明:我们查看微博的时候,都会看到查看了一定版面的微博之后,会出现一个加载的动画,过一会之后会出现新的微博。这个加载的过程就是Ajax加载。

Ajax请求步骤:

  • 发送请求
  • 解析内容
  • 渲染网页
  • JSON

还有一个需要介绍的概念(别怪我啰嗦,因为我也是小白,对web知识了解的也不多,边做边学)是JSON,这个学术上的名称是JavaScript Object Notation,本质上说他是一种传递对象的语法,对象可以是name/value键值对,数组和其他对象。

主要组成是:{}、[]、:、,

  • pycharm 相同目录下引用其他文件

pycharm不会将当前文件目录自动加入自己的source_path.右键make_directory as->source path 将当前工作的文件夹加入 source_path就可以了。

项目结构

​ 好了,之前我们说到了真实数据是Ajax请求得到的,如果想抓取这些数据,需要知道这些请求时怎么发送的,发送到哪里,发了哪些参数。请看Ajax分析:

  1. 查看请求

    在微博界面上按F12键,便可以弹出开发者模式。在Element选项卡中可以查看网页源代码,点击Network选项卡,之后再重新刷新页面,可以发现有很多条目,这个就是页面加载过程中浏览器与服务器之间发送请求和接受响应的所有记录。

    在这些记录里面查找Ajax请求的方法:我们可以在getIndex开头的信息里面找到一个,其Typexhr的请求类型,这就是Ajax

  2. 过滤请求

    浏览器里按F12出现开发者工具筛选功能筛选出所有的Ajax请求,在请求上方有一层筛选栏,直接点击XHR,此时下方显示的所有请求就是 Ajax请求了。向下滑动网页,可以看到有新的Ajax请求出现。

这个项目说实话是一个比较基本的爬虫练手项目,目的是爬取今日头条上的美女图片。主要的流程框架是:

  • 抓取索引页内容

    利用request请求目标站点,得到索引网页HTML代码,返回结果。

  • 抓取详情页内容

    解析返回结果,得到详情页的链接,并进一步抓取详情页的信息

  • 开启循环及多线程

    对多页内容遍历,开启多线程提高抓取速度

  • 下载图片与保存数据库

    将图片下载到本地,并把页面信息及图片URL保存至MongoDB

好了,介绍完大体架构了,然后就要开始实操了。我用的是pycharm来写python代码的。界面比较友好,这里推荐一下。然后再介绍一下pycharm的快捷键,方便大家快速便捷地写出优雅的代码。
Pycharm Windows快捷键

代码分析

代码文件总共是两个,一个是 spider.py,一个是 config.py文件。

# 在URL中没有相应的搜索字段,可以判断网页内容是`Ajax`加载,然后用`JavaScript`渲染出来的,接下来切
#换到 XHR过滤选项卡,可以查看`Ajax`请求。可以看到请求是属于GET请求的,请求URL的参数有offset,#format,keyword,autoload,count和cur_tab 需要找出这些参数的规律,这样才能方便地构造程序。
import json
import os
from urllib.parse import urlencode
import pymongo
import requests
from bs4 import BeautifulSoup
from requests.exceptions import ConnectionError
import re
from multiprocessing import Pool
from hashlib import md5
from json.decoder import JSONDecodeError
from config import *

#用来和MongoDB数据库进行连接
client = pymongo.MongoClient(MONGO_URL, connect=False)
db = client[MONGO_DB]


#获取URL内容
def get_page_index(offset, keyword):
    data = {
        'autoload': 'true',
        'count': 20,
        'cur_tab': 3,
        'format': 'json',
        'keyword': keyword,
        'offset': offset,
    }
    params = urlencode(data)
    base = 'http://www.toutiao.com/search_content/'
    url = base + '?' + params
    try:
        response = requests.get(url)
        if response.status_code == 200:
            return response.text
        return None
    except ConnectionError:
        print('Error occurred')
        return None

# 下载图片
def download_image(url):
    print('Downloading', url)
    try:
        response = requests.get(url)
        if response.status_code == 200:
            save_image(response.content)
        return None
    except ConnectionError:
        return None

# 保存图片
def save_image(content):
    file_path = '{0}/{1}.{2}'.format(os.getcwd(), md5(content).hexdigest(), 'jpg')
    print(file_path)
    if not os.path.exists(file_path):
        with open(file_path, 'wb') as f:
            f.write(content)
            f.close()

# 解析导航页
def parse_page_index(text):
    try:
        data = json.loads(text)
        if data and 'data' in data.keys():
            for item in data.get('data'):
                yield item.get('article_url')
    except JSONDecodeError:
        pass

# 获取详情页
def get_page_detail(url):
    try:
        response = requests.get(url)
        if response.status_code == 200:
            return response.text
        return None
    except ConnectionError:
        print('Error occurred')
        return None

# 解析详情页
def parse_page_detail(html, url):
    soup = BeautifulSoup(html, 'lxml')
    result = soup.select('title')
    title = result[0].get_text() if result else ''
    images_pattern = re.compile('gallery: JSON.parse\("(.*)"\)', re.S)
    result = re.search(images_pattern, html)
    if result:
        data = json.loads(result.group(1).replace('\\', ''))
        if data and 'sub_images' in data.keys():
            sub_images = data.get('sub_images')
            images = [item.get('url') for item in sub_images]
            for image in images: download_image(image)
            return {
                'title': title,
                'url': url,
                'images': images
            }

# 存储到数据库
def save_to_mongo(result):
    if db[MONGO_TABLE].insert(result):
        print('Successfully Saved to Mongo', result)
        return True
    return False


def main(offset):
    text = get_page_index(offset, KEYWORD)
    urls = parse_page_index(text)
    for url in urls:
        html = get_page_detail(url)
        result = parse_page_detail(html, url)
        if result: save_to_mongo(result)

# 这里定义了分页的起始页数和终止页数,分别GROUP_START 和 GROUP_END,利用了多线程的线程池,并调用#`map`函数实现多线程下载
if __name__ == '__main__':
    pool = Pool()
    groups = ([x * 20 for x in range(GROUP_START, GROUP_END + 1)])
    pool.map(main, groups)
    pool.close()
    pool.join()
MONGO_URL = 'localhost'
MONGO_DB = 'toutiao'
MONGO_TABLE = 'toutiao'

GROUP_START = 1
GROUP_END = 20
KEYWORD='街拍'

参考文献

你有哪些想要分享的 PyCharm 使用技巧?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值