python爬虫学习笔记分析Ajax爬取果壳网文章

有时在使用requests抓取页面会遇到得到的结果与在浏览器 中看到的结果不一样,在浏览器检查元素中可以看到的正常的显示的网页数据,但是requests请求得到的结果却没有。这是因为requests请求得到的时原始的html文档,而浏览器中的界面确实经过JavaScript处理数据生成的结果,这些数据来源可能不同,有的时Ajax加载的,可能包含在html文档中,也有可能经过JavaScript渲染得到的。
对于Ajax(全称:Asynchronous JavaScript and XML),即异步的JavaScript和XML,是一种利用JavaScript技术在保证页面不刷新的情况下与服务器交换数据并更新部分网页的技术。
对于没有使用Ajax技术的网页来说,要想更新内容,就必须刷新整个页面,但使用Ajax就可以在后台完成与服务器的数据交互,获取到数据之后,再利用JavaScript改变网页即可。
这里以果壳网科学人为例,url为:“https://www.guokr.com/scientific/”,在文章下拉中我们并没有发现有翻页的操作,但会出现一个加载动画,然后下方又出现了新的内容,这个过程就是Ajax加载的过程,而网页的连接并没有改变。
在这里插入图片描述Ajax加载动画

Ajax的分析:

Ajax具体又是如何实现这一过程的呢。从发送Ajax请求到网页更新过程中其实可以简单分为一下3个部分:
1)发送请求;
2)解析内容;
3)渲染网页
这个过程基本都需要以来JavaScript来实现,因为不是专业的,这里就不班门弄斧了,下面具体来说一下分析方法;
以chrome浏览器来介绍,打开链接:“https://www.guokr.com/scientific/”,打开开发者工具,并切换到network选项卡,如下图所示:
在这里插入图片描述network面板结果
这里可以看到非常多的条目,但Ajax的请求是一种特殊的类型,叫做xhr,在选项栏直接选区XHR,则剩下显示的就都是Ajax请求了,并且随着下滑在下方会不断的出现新的Ajax请求,下面就可以通过分析这个请求去实现数据的爬取了。(在请求头信息中发现X-Requested-With: XMLHttpRequest字段即为Ajax请求)
选定其中一个请求,进入详情界面,如下图:
在这里插入图片描述可以发现这是一个GET类型的请求,请求的链接为:’https://www.guokr.com/apis/minisite/article.json?retrieve_type=by_subject&limit=20&offset=38&_=1545367819629‘,请求的参数有3个:retrieve_type,limit和offset,通过查看其他请求发现retrieve_type,limit始终如一,改变的只有offset值,即控制分页的参数,且规律很简单,第一页为18,此后每一页增加20.
接下来观察这个请求的响应内容,即preview界面,如下图:
在这里插入图片描述响应内容
可以发现这个响应内容是JSON格式的,浏览器帮我们做了解析,可以看到信息就是在result里面,这里我们只需要得到文章的链接即可,即url字段,然后在每一个详情页面对文章内容解析。
接下来是具体代码的实现:
首先定义一个方法来获取每次请求的结果,在请求时,将offset作为参数传入来构造url,代码如下:

import requests
from urllib.parse import urlencode
def get_index(offset):
    base_url = 'http://www.guokr.com/apis/minisite/article.json?'
    data = {
        'retrieve_type': "by_subject",
        'limit': "20",
        'offset': offset
    }
    url = base_url + urlencode(data)
    #print(url)
    try:
        headers={'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36'}
        resp = requests.get(url,headers=headers)
        if codes.ok == resp.status_code:
            return resp.json()
    except requests.ConnectionError:
        return None

定义了base_url来实现url前半部分,构造data字典,使用urlencode()方法将参数转换为GET请求参数,实现url的拼接,使用requests的get()方法请求链接,判断状态码为200后,返回json格式的结果。
下面定义一个方法去解析出每篇文章对应的链接:

import json
def get_url(json):
    if json.get('result'):
        result = json.get('result')
        for item in result:
            yield item.get('url')

在返回的内容中得到result,然后遍历它得到文章的url。
通过得到的文章链接欸,然后就可以在新页面解析得到文章具体内容,这里我们获取文章标题,作者以及正文,代码如下:

from bs4 import BeautifulSoup as bsp
def get_text(url):
    html=requests.get(url).text
    print(html)
    soup=bsp(html,'lxml')
    title=soup.find('h1',id='articleTitle').get_text()
    autor=soup.find('div',class_="content-th-info").find('a').get_text()
    article_content=soup.find('div',class_="document").find_all('p')
    all_p = [i.get_text() for i in article_content if not i.find('img') and not i.find('a')]#去除标签
    article = '\n'.join(all_p)
    yield {"title":title,"autor":autor,"article":article}

这里使用BeautifulSoup来解析html,返回了title,autor以及article字段,其中去除标签的方法可以借用pyquery库的text()方法来实现。

最后时文章的保存,我们选择保存为txt文本格式,每一篇文章为一个文件,以获取的title字段命名即可,代码如下:

def save_article(content):
    try:
        if content.get('title'):
            filename=str(content.get('title'))+'.txt'
            with open(file_name, 'w',encoding='utf-8') as f:
                #f.write(json.dumps(content,ensure_ascii=False))
                f.write('\n'.join([str(content.get('title')),str(content.get('autor')),str(content.get('article'))]))
                print('Downloaded article path is %s' % filename)
        else:
            print('Already Downloaded', file_path)
    except requests.ConnectionError:
        print('Failed to Save Image,item %s' % content)

这样就实现了获取文章的保存,效果如下图:
在这里插入图片描述
当然我们也可以将其保存到mongodb数据库中,代码如下:

from pymongo import MongoClient
client=MongoClient()
db=client['guoke']
collection=db['guoke']
def save_to_mongo(content):
	if collection.insert(dict(content)):
		print('saved to mongo' %content.get('title'))

最后通过传入offset来实现翻页爬取,代码如下:

import time
def main(offset):
    result=get_index(offset)
    all_url=get_url(result)
    for url in all_url:
        article=get_text(url)
        for art in article:
            #print(art)
            save_article(art)


GROUP_START = 0
GROUP_END = 10

if __name__ == '__main__':
    for i in range(GROUP_START,GROUP_END+1):
        main(offset=i*20+18)
        time.sleep(1)

这样虽然也可以实现功能,但因为还是一个进程,会导致速度很慢,这里我们选择使用进程池的方法,将代码可以修改为:

GROUP_START = 0
GROUP_END = 10
from multiprocessing.pool import Pool
if __name__ == '__main__':
    pool=Pool()
    pool.map(main,[x*20+18 for x in range(GROUP_START,GROUP_END+1)])
    time.sleep(1)

这里通过GROUP_START,GROUP_END两个参数来控制爬取的页数.因为没有使用代理,这里加入一个休眠时间防止请求过于频繁。
这样就实现了Ajax加载页面的数据爬取,初步做到了可见即可爬的设想。
ps:github地址如下:‘https://github.com/linl12138/Ajax_guoke’

猜你喜欢

转载自blog.csdn.net/weixin_42672765/article/details/85160637