使用puppeteer爬取IATA 航空公司数据

背景

从iata 网页中爬取全球航空公司的相关信息,目标页面:iata查询页面
image.png

数据爬取目标分析

  1. 分析目标网页的结构
  2. 确定爬取的锚点
  3. 确定有效数据

image.png
上图是浏览器开发者工具中显示的网页结构。我们可以分析出要提取有效数据,可以先找到.airlinecodesearchblock 然后再在这个div中找到tr,再对每一个tr遍历抽出td中的数据,每一行的数据就是我们要找的有效数据。

程序流程

image.png
画出程序流程图之后可看出,打开页面、获取页面数据、写入数据库可以抽象成函数来复用
打开页面的函数

async function openPage(pageHandle, pageNumber) {
    const _url = `${CHANGEURL}${pageNumber}&airline.search=`;
    console.log(_url);
    await pageHandle.goto(_url, {waitUntil: 'networkidle2'});
    let elementPath = '';
    elementPath = '.airlinecodessearchblock';
    await pageHandle.waitForSelector(elementPath);
}

获取页面数据函数

async function getPageData(pageHandle, pageNumber) {
    console.log(`开始解析第${pageNumber}的数据...`);
    const parseResult = await pageHandle.evaluate(() => {
        let flightList = [];
        const airlineBlock = document.querySelector('div.airlinecodessearchblock');
        const trItems = airlineBlock.querySelectorAll('tbody>tr');
        if (trItems.length > 0) {
            for (let i = 0; i < trItems.length; i++) {
                let tdItems = trItems[i].querySelectorAll('td');
                let data = {};
                data.companyName = tdItems[0].innerText.replace('\'', '');
                data.country = tdItems[1].innerText.replace('\'', '');
                data.code2 = tdItems[2].innerText;
                data.airlinePrefix = tdItems[3].innerText;
                data.pax = tdItems[4].innerText;
                flightList.push(data);
            }

        }
        return flightList;
    });
    console.log(`完成解析第${pageNumber}的数据...`);
    return parseResult;
}

写入数据库

async function writeDataToDB(airlineData, pageNumber) {
    console.log(`开始写入第${pageNumber}的数据...`);
    gDBHandle.connection = await gDBHandle.getConnection();
    await gDBHandle.beginTransaction();
    for (let i = 0; i < airlineData.length; i++) {
        let _data = airlineData[i];
        let dbRes;//数据库写入是否成功
        const {code2} = airlineData[i];
        dbRes = await gDBHandle.find({code2}, 1, ['id']);
        if (dbRes.data.length > 0) {
            const additionalData = {
                updateTime: getNowTimeFormat(),
            }
            _data = {...airlineData[i], ...additionalData};
            let res = await gDBHandle.update({id: dbRes.data[0].id}, _data);
            if (!res.code) {
                await gDBHandle.rollback();
                gErrorArr.add(pageNumber);
                break;
            }

        } else {
            let res = await gDBHandle.add(_data);
            if (!res.code) {
                try {
                    await gDBHandle.rollback();
                    gErrorArr.add(pageNumber);
                } catch (e) {
                    console.log(e);
                }
                break;
            }
        }
    }
    await gDBHandle.commit();
    console.log(`完成写入第${pageNumber}的数据...`);
}

数据库使用的是mysql,使用了mysql的连接池和事务技术。每一次数据库写入前先从连接池中获取一个连接,然后创建事务,如果写入数据库出错就回滚,但是回滚仅仅是丢掉了当前页的数据,并且将当前页的页码记录在一个数组中,方便再次爬取。

总体流程

async function initPage() {
    let gPageData = [];
    let startTime = new Date();
    globeBrowser = await puppeteer.launch(defaultConfig.config);
    if (globeBrowser) {
        const page = await globeBrowser.newPage();
        page.setDefaultNavigationTimeout(100000);
        await openPage(page, 1);
        gDBHandle = new DBTransaction('crawler_train_plane', 'tb_airline');
        const parseResult = await page.evaluate(() => {
            const airlineBlock = document.querySelector('div.airlinecodessearchblock');
            let pagination = airlineBlock.querySelector('div.pagination');
            let linkItems = pagination.querySelectorAll('a');
            let _pageCount = linkItems[linkItems.length - 2].innerText;
            return _pageCount;
        });
        let currPageData = await getPageData(page, 1);
        await writeDataToDB(currPageData, 1);
        const pageCount = parseResult;
        for (let i = 2; i <= pageCount; i++) {
            await openPage(page, i);
            let currPageData = await getPageData(page, i);
            await writeDataToDB(currPageData, i);
            let endTime = new Date();
            let str = getTimeDiff("爬取数据", startTime, endTime);
            console.log(str);
        }
    }
}

代码遗留问题

1.puppeteer大量使用async/await来实现同步代码,这样方便大家理解,但是爬取速度上有问题,没有发挥javascript异步的优势
2.程序中puppeteer仅仅打开了一个页面来进行爬取,可以预先打开多个页面来进行多页面爬取。

你可能感兴趣的:(puppeteer)