Skip to content

Commit bb37024

Browse files
authored
Merge pull request #1239 from DaanVanVugt/feature/taxila_citizen_science_scraper
add citizen_science scraper for taxila
2 parents 0475b44 + f2fdea6 commit bb37024

4 files changed

Lines changed: 530 additions & 1 deletion

File tree

lib/ingestors/ingestor_factory.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ def self.taxila_ingestors
4040
Ingestors::Taxila::SenseIngestor,
4141
Ingestors::Taxila::VuMaterialIngestor,
4242
Ingestors::Taxila::RdnlIngestor,
43-
Ingestors::Taxila::HanIngestor
43+
Ingestors::Taxila::HanIngestor,
44+
Ingestors::Taxila::CitizenScienceIngestor
4445
]
4546
end
4647

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# frozen_string_literal: true
2+
3+
require 'open-uri'
4+
require 'csv'
5+
require 'nokogiri'
6+
7+
module Ingestors
8+
module Taxila
9+
class CitizenScienceIngestor < Ingestor
10+
def self.config
11+
{
12+
key: 'citizen_science_event',
13+
title: 'CitizenScience Events API',
14+
category: :events
15+
}
16+
end
17+
18+
def read(url)
19+
begin
20+
process_citizen_science_events(url)
21+
process_citizen_science_materials(url)
22+
rescue Exception => e
23+
@messages << "#{self.class.name} failed with: #{e.message}"
24+
end
25+
26+
# finished
27+
nil
28+
end
29+
30+
private
31+
32+
def process_citizen_science_events(_url)
33+
citizen_science_url = 'https://citizenscience.nl/events/'
34+
overview_page = Nokogiri::HTML5.parse(open_url(citizen_science_url.to_s, raise: true))
35+
.at_xpath("//h1[normalize-space(.)='Aankomende evenementen']")
36+
&.ancestors('.container')&.first
37+
&.css('.row')&.first
38+
&.css('.card')
39+
40+
return if overview_page.nil?
41+
42+
overview_page.each_with_index do |el, _idx|
43+
event = OpenStruct.new
44+
event.url = el.css('a.btn').first['href']
45+
event.title = el.css('p.project-name').text.strip
46+
date_str = el.css('.fa-calendar').first.parent.text.strip
47+
mapped_date_str = citizen_science_month_mapping(date_str)
48+
event.start = DateTime.parse(mapped_date_str)
49+
event.set_default_times
50+
event.venue = el.css('.fa-map-marker-alt').first.parent.text.strip
51+
event.description = el.css("div.half-content > p:not([style*='display: none']):not([hidden])").first.text.strip
52+
event.source = 'CitizenScience'
53+
event.timezone = 'Amsterdam'
54+
add_event(event)
55+
rescue Exception => e
56+
@messages << "Extract event fields failed with: #{e.message}"
57+
end
58+
end
59+
60+
def process_citizen_science_materials(_url)
61+
urls = [
62+
'https://citizenscience.nl/resources',
63+
'https://citizenscience.nl/training_resources'
64+
]
65+
66+
urls.each do |citizen_science_url|
67+
3.times do |i|
68+
new_url = "#{citizen_science_url}?page=#{i + 1}"
69+
sleep(1) unless Rails.env.test? and File.exist?('test/vcr_cassettes/ingestors/citizen_science.yml')
70+
overview_page = Nokogiri::HTML5.parse(open_url(new_url.to_s, raise: true))
71+
.css('.project-card')
72+
73+
overview_page.each_with_index do |el, _idx|
74+
material = OpenStruct.new
75+
material.url = "https://www.citizenscience.nl#{el.css('h3.project-name').first.parent['href']}"
76+
material.title = el.css('h3.project-name').first.text.strip
77+
material.description = el.css(".project-description").first.text.strip
78+
add_material(material)
79+
rescue Exception => e
80+
@messages << "Extract material fields failed with: #{e.message}"
81+
end
82+
end
83+
end
84+
end
85+
86+
def citizen_science_month_mapping(str)
87+
mapping = [
88+
%w[Mrt Mar],
89+
%w[Mei May],
90+
%w[Okt Oct]
91+
]
92+
mapping.each do |dutch, english|
93+
str = str.gsub(dutch, english)
94+
end
95+
str
96+
end
97+
end
98+
end
99+
end
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
require 'test_helper'
2+
3+
class CitizenScienceIngestorTest < ActiveSupport::TestCase
4+
setup do
5+
@user = users(:regular_user)
6+
@content_provider = content_providers(:another_portal_provider)
7+
mock_ingestions
8+
mock_timezone
9+
end
10+
11+
test 'can ingest events from citizen_science' do
12+
source = @content_provider.sources.build(
13+
url: 'https://citizenscience.nl/events/',
14+
method: 'citizen_science',
15+
enabled: true
16+
)
17+
18+
ingestor = Ingestors::Taxila::CitizenScienceIngestor.new
19+
20+
# check event doesn't exist yet
21+
new_title = 'High-Level Policy Event on the Sustainability of Citizen Science'
22+
new_url = 'https://events.teams.microsoft.com/event/47581424-c548-4f07-928a-9fed358df416@659b3608-37a1-406b-9e1a-02c011decd3c'
23+
refute Event.where(title: new_title, url: new_url).any?
24+
25+
# run task
26+
assert_difference 'Event.count', 13 do
27+
freeze_time(2023) do
28+
VCR.use_cassette("ingestors/citizen_science") do
29+
ingestor.read(source.url)
30+
ingestor.write(@user, @content_provider)
31+
end
32+
end
33+
end
34+
35+
assert_equal 13, ingestor.events.count
36+
assert_equal 13, ingestor.stats[:events][:added]
37+
assert_equal 0, ingestor.stats[:events][:updated]
38+
assert_equal 0, ingestor.stats[:events][:rejected]
39+
40+
# check event does exist
41+
event = Event.where(title: new_title, url: new_url).first
42+
assert event
43+
assert_equal new_title, event.title
44+
assert_equal new_url, event.url
45+
46+
# check other fields
47+
assert_equal 'CitizenScience', event.source
48+
assert_equal 'Amsterdam', event.timezone
49+
assert_equal Time.zone.parse('Mon, 19 Feb 2026 09:00:00.000000000 UTC +00:00'), event.start
50+
assert_equal Time.zone.parse('Mon, 19 Feb 2026 10:00:00.000000000 UTC +00:00'), event.end
51+
assert_equal 'Online', event.venue
52+
assert event.online?
53+
end
54+
55+
test 'can ingest materials from citizen_science' do
56+
source = @content_provider.sources.build(
57+
url: 'https://citizenscience.nl/events/',
58+
method: 'citizen_science',
59+
enabled: true
60+
)
61+
62+
ingestor = Ingestors::Taxila::CitizenScienceIngestor.new
63+
64+
# check material doesn't exist yet
65+
new_title = 'Naar een vaste plek voor burgerwetenschap in het netwerk van openbare bibliotheken'
66+
new_url = 'https://www.citizenscience.nl/resource/475'
67+
refute Material.where(title: new_title, url: new_url).any?
68+
69+
# run task
70+
assert_difference 'Material.count', 25 do
71+
freeze_time(2023) do
72+
VCR.use_cassette("ingestors/citizen_science") do
73+
ingestor.read(source.url)
74+
ingestor.write(@user, @content_provider)
75+
end
76+
end
77+
end
78+
79+
assert_equal 33, ingestor.materials.count
80+
assert_equal 25, ingestor.stats[:materials][:added]
81+
assert_equal 8, ingestor.stats[:materials][:updated]
82+
assert_equal 0, ingestor.stats[:materials][:rejected]
83+
84+
# check material does exist
85+
material = Material.where(title: new_title, url: new_url).first
86+
assert material
87+
assert_equal new_title, material.title
88+
assert_equal new_url, material.url
89+
end
90+
end

test/vcr_cassettes/ingestors/citizen_science.yml

Lines changed: 339 additions & 0 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)