Showing posts with label blog. Show all posts
Showing posts with label blog. Show all posts

Monday, 11 January 2016

The GraphBlogGraph: 2nd blogpost out of 3

Importing the GraphBlogGraph into Neo4j

In the previous part of this blog-series about the GraphBlogGraph, I talked a lot about creating the dataset for creating what I wanted: a graph of blogs about graphs. I was able to read the blog-feeds of several cool graphblogs with a Google spreadsheet function called “ImportFEED”, and scrape their pages using another function using “ImportXML”. So now I have the sheet ready to go, and we also know that with a Google spreadsheet, it is really easy to download that as a CSV file:

You then basically get a URL for the CSV file (from your browser’s download history):

and that gets you ready to start working with the CSV file:

I can work with that CSV file in Cypher’s LOAD CSV command, as we know. All we really need is to come up with a solid Graph Model to do what we want to do. So I went to Alistair’s Arrows, and drew out a very simple graph model:



So that basically get’s me ready to start working with the CSV files in Cypher. Let’s run through the different import commands that I ran to do the imports. All of those are on github of course, but I will take you through them here too...

First create the indexes

create index on :Blog(name);
create constraint on (p:Page) assert p.url is unique;

Then manually create the blog-nodes:

create (b:Blog {name:"Bruggen", url:"http://blog.bruggen.com"});
create (n:Blog {name:"Neo4j Blog", url:"http://neo4j.com/blog"});
create (n:Blog {name:"JEXP Blog", url:"http://jexp.de/blog/"});
create (n:Blog {name:"Armbruster-IT Blog", url:"http://blog.armbruster-it.de/"});
create (n:Blog {name:"Max De Marzi's Blog", url:"http://maxdemarzi.com/"});
create (n:Blog {name:"Will Lyon's Blog", url:"http://lyonwj.com/"});

I could have done that from a CSV file as well, of course. But hey - I have no excuse - I was lazy :) … Again…

Then I can start with importing the pages and links for the first (my own) blog, which is at blog.bruggen.com and has a feed at blog.bruggen.com/feeds/posts/default:

//create the Bruggen blog entries
load csv with headers from "https://docs.google.com/a/neotechnology.com/spreadsheets/d/1LAQarqQ-id74-zxV6R4SdG7mCq_24xACXO5WNOP-2_w/export?format=csv&id=1LAQarqQ-id74-zxV6R4SdG7mCq_24xACXO5WNOP-2_w&gid=0" as csv
match (b:Blog {name:"Bruggen", url:"http://blog.bruggen.com"})
create (p:Page {url: csv.URL, title: csv.Title, created: csv.Date})-[:PART_OF]->(b);

This just creates the 20 leaf nodes from the Blog node. The fancy styff happens next, when I then read from the “Links” column, holding the “****”-separated links to other pages, split them up into individual links, and merge the pages and create the links to them. I use some fancy Cypher magic that I have also used before for Graph Karaoke: I read the cell, and then split the cell into parts and put them into a collection, and then unwind the collection and iterate through it using an index:

//create the link graph
load csv with headers from "https://docs.google.com/a/neotechnology.com/spreadsheets/d/1LAQarqQ-id74-zxV6R4SdG7mCq_24xACXO5WNOP-2_w/export?format=csv&id=1LAQarqQ-id74-zxV6R4SdG7mCq_24xACXO5WNOP-2_w&gid=0" as csv
with csv.URL as URL, csv.Links as row
unwind row as linklist
with URL, [l in split(linklist,"****") | trim(l)] as links
unwind range(0,size(links)-2) as idx
MERGE (l:Page {url:links[idx]})
WITH l, URL
MATCH (p:Page {url: URL})
MERGE (p)-[:LINKS_TO]->(l);

So this first MERGEs the new pages (finds them if they already exist, creates them if they do not yet exist) and then MERGEs the links to those pages. This creates a LOT of pages and links, because of course - like with every blog - there’s a lot of hyperlinks that are the same on every page of the blog (essentially the “template” links that are used over and over again).
And as you can see it looks a little bit like a hairball when you look at it in the Neo4j Browser:
So in order to make the rest of our GraphBlogGraph explorations a bit more interesting, I decided that it would be useful to do a bit of cleanup on this graph. I wrote a couple of Cypher queries that remove the “uninteresting”, redundant links from the Graph:

//remove the redundant links
//linking to pages with same url (eg. archive pages, label pages...)
match (b:Blog {name:"Bruggen"})<-[:PART_OF]-(p1:Page)-[:LINKS_TO]->(p2:Page)
where p2.url starts with "http://blog.bruggen.com"
and not ((b)<-[:PART_OF]-(p2))
detach delete p2;
//linking to other posts of the same blog
match (p1:Page)-[:PART_OF]->(b:Blog {name:"Bruggen"})<-[:PART_OF]-(p2:Page),
(p1)-[lt:LINKS_TO]-(p2)
delete lt;

//linking to itself
match (p1:Page)-[:PART_OF]->(b:Blog {name:"Bruggen"}),
(p1)-[lt:LINKS_TO]-(p1)
delete lt;

//linking to the blog provider (Blogger)
match (p:Page)
where p.url contains "//www.blogger.com"
detach delete p;

Which turned out to be pretty effective. When I run these queries I weed out a lot of “not so very useful” links between nodes in the graph.
And the cleaned-up store looks a lot better and workable.

If you take a look at the import script on github, you will see that there’s a similar script like the one above for every one of the blogs that we set out to import. Copy and paste that into the browser one by one, the neo4j shell, or use LazyWebCypher, and have fun:
So that’s it for the import part. Now there’s only one thing left to do, in Part 3/3 of this blogpost series, and that is to start playing around with some cool queries. Look that post in the next few days.

Hope this was interesting for you.

Cheers

Rik

Thursday, 7 January 2016

The GraphBlogGraph: 1st blogpost out of 3

Making the GraphBlogGraph

For quite a few years now, I have been hosting my own blog at blog.bruggen.com. It’s been quite an interesting experience I must say. Long time ago, I started blogging as kind of a personal diary kind of thing, but… then Facebook and Twitter happened, and it seemed kind of redundant at the time. Then I got to work for Neo4j, and got stuck into the Neo4j community, and I “restarted” my blog to write about my life and work in the Neo4j community. It’s been a very, very fun ride.


So this past Christmas period I had to a bunch of work for my Orienteering club, and I do most of that work (registering club members, registering for races, managing billing etc) in Google Sheets. And I came across acouple of really interesting things that I did not knew existed - and that I thought would make a super cool Graph application. The two things were:
  • an easy and automated way to read a blog “feed” (in Atom or RSS) and put the items into a Google Sheet. This is called the “ImportFEED” function:
    here's the manual - it’s a really interesting piece of functionality.
  • an easy and automated way to pars XML (and therefore, HTML pages) and extract information from that XML using XPATH. This function is called ImportXML:

So my idea was basically very simple: why don’t I use this functionality to read the feeds from a couple of Neo4j-centric blogs that I know (using ImportFEED), and then use the URLs of the pages in the feed to scrape the HTML of the blogpost page with ImportXML, and extract the hyperlinks (<a href=”...”> tags in HTML). That way I could basically look at the graph of links between the different blogs, and see if I could discover anything interesting...


So I did. I will publish a couple of blogposts (!) in the next few days to explain the story.

Reading the GraphBlog-feeds

I got to work. I created a google sheet (which is publicly available for you to view and copy if you want), and listed some of the blogs that I would be interested in.

NameURLFeed
Rik Van Bruggenhttp://blog.bruggen.comhttp://blog.bruggen.com/feeds/posts/default
Michael Hungerhttp://jexp.de/bloghttp://jexp.de/blog/feed/
Stefan Armbrusterhttp://blog.armbruster-it.dehttp://blog.armbruster-it.de/feed/
Neo4j.comhttp://neo4j.com/bloghttp://neo4j.com/feed/
Max De Marzihttp://maxdemarzi.comhttp://maxdemarzi.com/feed/
Mark Needhamhttp://www.markhneedham.com/blog/http://feeds.feedburner.com/markneedham
Will Lyonhttp://www.lyonwj.com/http://www.lyonwj.com/atom.xml

I had some others on the list (Linkurio.us blog, GraphAware blog, GrapheneDB blog) but I could not immediately find the feeds of these blogs… maybe some day :)) …


So the next thing I did was I used ImportFEED to load the data of these feeds into a sheet of the workbook. The feeds actually look like this:
But with the ImportFEED function, it is really trivial to get that into a workable format. I used the following three formulae to load the created date (“items created”), the title (“items title”) and the URL (“items url”) of the last 20 posts in the feed into three colums:


=importfeed("http://blog.bruggen.com/feeds/posts/default","items created",TRUE, 20)
=importfeed("http://blog.bruggen.com/feeds/posts/default","items title",TRUE, 20)
=importfeed("http://blog.bruggen.com/feeds/posts/default","items url",TRUE, 20)


The result was actually super cool: a sheet for every blog that had date, title and url information for this particular blog.


Crawling the GraphBlog-pages

Then, next, I wanted to do some webpage crawling/scraping/whatever you want to call it with ImportXML. So that’s why I have the following formula:


=IMPORTXML(D2, "//a/@href")


Which is giving me and array like so:


Now, what I obviously want to do later on is import these things into a graph database, so I really wanted to get all of these links together into a “big” cell. So I decided to use a JOIN function to do that:
Whit the following JOIN I can actually put all these links into a cell of the spreadsheet, separated by a delimiter (“****” in this case):


=join("****",sort(unique(IMPORTXML(D2, "//a/@href"))))


By doing it this way, each of these cells we get a long piece of text string:


//www.blogger.com/rearrange?blogID=4466865603389367352&widgetType=Attribution&widgetId=Attribution1&action=editWidget&sectionId=footer-3****//www.blogger.com/rearrange?blogID=4466865603389367352&widgetType=BlogArchive&widgetId=BlogArchive1&action=editWidget&sectionId=sidebar-right-1****//www.blogger.com/rearrange?blogID=4466865603389367352&widgetType=Label&widgetId=Label1&action=editWidget&sectionId=sidebar-right-1****//www.blogger.com/rearrange?blogID=4466865603389367352&widgetType=PageList&widgetId=PageList1&action=editWidget&sectionId=crosscol****http://blog.bruggen.com/****http://blog.bruggen.com/2013_01_01_archive.html****http://blog.bruggen.com/2013_03_01_archive.html****http://blog.bruggen.com/2013_04_01_archive.html****http://blog.bruggen.com/2013_05_01_archive.html****http://blog.bruggen.com/2013_06_01_archive.html****http://blog.bruggen.com/2013_07_01_archive.html****http://blog.bruggen.com/2013_08_01_archive.html****http://blog.bruggen.com/2013_09_01_archive.html****http://blog.bruggen.com/2013_10_01_archive.html****http://blog.bruggen.com/2013_11_01_archive.html****http://blog.bruggen.com/2013_12_01_archive.html****http://blog.bruggen.com/2014_01_01_archive.html****http://blog.bruggen.com/2014_02_01_archive.html****http://blog.bruggen.com/2014_03_01_archive.html****http://blog.bruggen.com/2014_04_01_archive.html****http://blog.bruggen.com/2014_05_01_archive.html****http://blog.bruggen.com/2014_06_01_archive.html****http://blog.bruggen.com/2014_07_01_archive.html****http://blog.bruggen.com/2014_08_01_archive.html****http://blog.bruggen.com/2014_09_01_archive.html****http://blog.bruggen.com/2014_10_01_archive.html****http://blog.bruggen.com/2014_11_01_archive.html****http://blog.bruggen.com/2014_12_01_archive.html****http://blog.bruggen.com/2015_01_01_archive.html****http://blog.bruggen.com/2015_02_01_archive.html****http://blog.bruggen.com/2015_03_01_archive.html****http://blog.bruggen.com/2015_04_01_archive.html****http://blog.bruggen.com/2015_05_01_archive.html****http://blog.bruggen.com/2015_06_01_archive.html****http://blog.bruggen.com/2015_07_01_archive.html****http://blog.bruggen.com/2015_08_01_archive.html****http://blog.bruggen.com/2015_09_01_archive.html****http://blog.bruggen.com/2015_10_01_archive.html****http://blog.bruggen.com/2015_11_01_archive.html**** etc etc etc


Which is fine, because I know how to split this cell into individual “blog links” again. What I have now is a spreadsheet containing the blog feed, and all of the links that go from the individual blog pages to other pages. Nice!


In the next section I will be importing that spreadsheet into Neo4j, and then we can start playing around with it.


I hope you enjoyed this blogpost so far. I will publish part 2 in a few days, for sure.


Cheers

Rik