npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

graze

v2.3.2

Published

Turn websites into well structured JSON using templates

Downloads

4

Readme

graze

A simple template-based web scraping library for node.js

Web scraping is a process which can leave even the most hardy developer with an unshakable feeling of disgust. Graze tries to make web scraping as undisgusting as possible, by letting you construct a template that just takes a URL in, and gives a well structured Javascript object out.

Quickstart

It's easiest to understand how it all works by looking at an example (in Coffeescript, if you don't mind).

graze = require 'graze'

template = graze.template {
    '#searchResult tr': 
        results: [
            'td:nth-child(2)':
                '.detName a':
                    title: graze.text()
                    link: graze.attr('href')
                '[alt="Magnet link"]':
                    magnet_link: graze.parent().attr('href')
                '.detDesc':
                    description: graze.text()
                    size: ($el) -> $el.text().match(/Size\s*([^,]*),/)?[1]
            'td:nth-child(3)':
                seeders: graze.text()
        ]
}

# Simple usage
template.scrape('http://thepiratebay.se/search/something%20illegal/').then (data) ->
    console.log data

# Advanced usage
template.scrape
  uri: 'https://securewebsite.com/user/transactions'
  auth:
    username: 'admin'
    password: 'itsasecret'
.then (data) ->
    console.log data
.catch ({error, response, body}) ->
    # Handle error

###
Output on success:

{
    "results": [ {
        "title": "Chairlift - Something",
        "link": "http://thepiratebay.se/torrent/9360343/Chairlift_-_Something",
        "magnet_link": "magnet:?xt=urn:btih:1e4dc0a30c6c413c947bd7df11bc8bd764c3babd",
        "description": "Uploaded 12-17 17:53, Size 39.79 MiB, ULed by Anonymous",
        "size": "39.79 MiB",
        "seeders": 1,
    }, {
        ...
    } ]
}
###

A template is just a Javascript Object, structured hierarchically in a way similar to the HTML you're trying to scrape. The keys and values of the objects that make up this template have special meanings. There are two kinds of meanings that keys can take on:

  • A CSS Selector: This changes the scope of the template that follows. At first the entire web page is within scope, but when a CSS selector is encountered as a key, the scope is refined to the elements that match the selector. The value of this key must be a plain Javascript Object, representing a nested template.
  • A name: This specifies the key that will be used in the scraped results Object. You'll get different scraping behaviour depending on the type of its value:
    • A graze extractor: This defines how to extract information from the HTML. They might look familiar if you've used jQuery. Pretend that the graze module is a jQuery object containing the elements in scope. Whatever chain of methods you call here will be repeated on the web page's in-scope elements, and the final result will appear in the output object under the above name.
    • A function: A function which takes in a jQuery object, and returns the desired information. Use a function if a graze extractor doesn't quite cut it. If you need to get fancy, you can have the function take in two arguments, like ($el, $), which gives you direct access to jQuery's $.
    • An array: This represents an iterator, which iterates over all the elements in scope and applies the template or extractor within to each element. The actual array you define here in is only of length 1, and contains the template, graze extractor, or function which will be evaluated with each element. The final result will be an array containing the information scraped from each iterated element. You can also nest arrays within arrays to iterate over deep structures.
    • None of the above: If you pass in a string or a number for example, it will simply reappear in the scraped results.

Scraping a website is as simple as calling the template.scrape function above. It takes the same first argument as the request function in the popular library request. It returns a q promise object, which helps to stop pyramids of death and makes error handling far more sane.

Pro tips

Nesting templates

You might find yourself in a situation where you'd like to group properties together within a parent object, to give something like:

{
    ...
    "properties": {
        "size": "1.2GB",
        "upload_date": "11/11/2011"
    },
    ...
}

This can be achieved using the special graze.nest(template) function, which takes in a template and returns a custom function. This function takes in the current page scope and processes it using the given template, returning that as its extracted value. So to achieve the above, you might create a template like:

graze.template {
    ...
    properties: graze.nest
        '.detDesc':
            size: ($el) -> $el.text().match(/Size\s*([^,]*),/)?[1]
            upload_date: ($el) -> $el.text().match(/Uploaded ([^,]*),/)?[1]
    ...
}

Accessing the response object

Custom functions are called with the request's response object as their context, so you can access things like the URL of the page being scraped as this.request.href.

Processing HTML directly

If you'd rather not have graze handle your web-page retrieval, you can manually run a template on an HTML string by calling template.process(html, context), where context is the context you wish your custom functions to run within.

Javascript example

For those of you who prefer decaf:

var graze = require('graze');

var template = graze.template({
    '#searchResult tr': {
        results: [{
            'td:nth-child(2)': {
                '.detName a': {
                    title: graze.text(),
                    link: graze.attr('href'),
                },
                '[alt="Magnet link"]': {
                    magnet_link: graze.parent().attr('href'),
                },
                '.detDesc': {
                    description: graze.text(),
                    size: function($el) { match = $el.text().match(/Size\s*([^,]*),/); return match && match[1] }
                },
            },
            'td:nth-child(3)': {
                seeders: graze.text(),
            },
        }],
    },
});

template.scrape({
    uri: 'https://securewebsite.com/user/transactions',
    auth: {
        username: 'admin',
        password: 'itsasecret',
    },
}).then(function(data) {
    console.log(data)
}).fail(function(data) {
    console.error(data.error, data.response, data.body)
});