17 | ]]>
18 | Sun, 3 Jul 2016 11:07:00 PDT
19 |
20 |
21 |
22 | Pine Fire continues to burn in wilderness area north of Ojai
23 | Alice Walton
24 | http://www.latimes.com/la-me-pine-fire-update-20160703-snap-story.html
25 | Hundreds of firefighters continued to battle a wildfire in the Sespe Wilderness north of Ojai on Sunday with no containment in sight.
The Pine fire started Thursday morning about 11 miles north of Ojai. As of Sunday morning, the fire had consumed 1,590 acres and was threatening 50 structures, said...
27 | ]]>
28 | Sun, 3 Jul 2016 11:05:00 PDT
29 |
30 |
31 | Ex-campaign manager dismisses complaints about Donald Trump's six-point-star tweet
32 | David Willman
33 | http://www.latimes.com/la-na-trailguide-updates-former-campaign-manager-dismisses-1467557105-htmlstory.html
34 |
37 | Sun, 3 Jul 2016 10:22:00 PDT
38 |
39 |
40 |
41 | GOP Sen. Cotton says Trump can 'make the case for himself'
42 | Christi Parsons
43 | http://www.latimes.com/la-na-trailguide-updates-gop-sen-cotton-says-trump-can-make-1467565002-htmlstory.html
44 |
47 | Sun, 3 Jul 2016 10:13:00 PDT
48 |
49 |
50 |
51 | Driver who escaped police pursuit in San Diego is finally arrested - two months later
52 | David Hernandez
53 | http://www.latimes.com/la-me-pursuit-arrest-20160703-snap-story.html
54 | Police say a man accused of leading officers on a chase through several freeways before ditching the car in downtown San Diego was arrested last week — two months after the pursuit.
Officials say Ahran Haugley, 41, drove off on April 28 when officers approached the Honda Accord in which he was...
56 | ]]>
57 | Sun, 3 Jul 2016 10:00:00 PDT
58 |
59 |
60 | FBI questions Hillary Clinton about her private email server
61 | http://www.latimes.com/la-na-trailguide-updates-07032016-htmlstory.html
62 |
65 | Sun, 3 Jul 2016 09:56:52 PDT
66 |
67 |
68 | Serena Williams cruises to third-round win at Wimbledon
69 | Associated Press
70 | http://www.latimes.com/la-sp-wimbledon-20160703-snap-story.html
71 | Serena Williams earned a decent day's rest on the middle Sunday at Wimbledon while Jo-Wilfried Tsonga had to work overtime — 19-17 in the fifth set — in another marathon involving John Isner.
Williams, the defending women's champion and six-time winner, overwhelmed Annika Beck, 6-3, 6-0, in just...
73 | ]]>
74 | Sun, 3 Jul 2016 09:50:00 PDT
75 |
76 |
77 |
78 | Lewis Hamilton wins Austrian Grand Prix after final-lap pass of Nico Rosberg
79 | Associated Press
80 | http://www.latimes.com/la-sp-formula-one-austrian-grand-prix-20160703-snap-story.html
81 | Lewis Hamilton won the Austrian Grand Prix on Sunday after colliding with Nico Rosberg on the final lap, an incident he blamed on his German teammate.
The two Mercedes drivers touched as Hamilton sought to overtake and Formula One championship leader Rosberg ended up losing his front wing, which...
83 | ]]>
84 | Sun, 3 Jul 2016 09:40:00 PDT
85 |
86 |
87 |
88 | Peter Sagan moves into Tour de France lead with Stage 2 win
89 | Associated Press
90 | http://www.latimes.com/la-sp-tour-de-france-20160703-snap-story.html
91 | World champion Peter Sagan made the most of a steep, short climb in a frenzied finale to win the second stage of the Tour de France and claim the race leader's yellow jersey on Sunday.
Sagan, who pulled on the coveted shirt for the first time, used his power on the 1.9-kilometer Cote de la Glacerie...
93 | ]]>
94 | Sun, 3 Jul 2016 09:30:00 PDT
95 |
96 |
97 |
98 | Man injured after explosion reported in Central Park
99 | Associated Press
100 | http://www.latimes.com/la-na-central-park-explosion-reported-20160703-snap-story.html
101 | Authorities say a man was seriously hurt in New York City’s Central Park after people near the area reported hearing some kind of explosion.
Fire officials say it happened shortly before 11 a.m., inside the park at 68th Street and Fifth Avenue. The man suffered serious injuries, possibly requiring...
103 | ]]>
104 | Sun, 3 Jul 2016 09:29:00 PDT
105 |
106 |
107 |
108 | At least 120 people - including 15 children - killed in dual Baghdad bombings
109 | Associated Press
110 | http://www.latimes.com/la-fg-ap-baghdad-bombing-20160702-snap-story.html
111 | A suicide truck bomb in downtown Baghdad killed 115 people and wounded nearly 200 others who were out shopping and celebrating early Sunday ahead of the holiday marking the end of Ramadan, security and medical officials said.
The attack, claimed by Islamic State, was the deadliest in months in...
113 | ]]>
114 | Sun, 3 Jul 2016 09:16:00 PDT
115 |
116 |
117 |
118 | Possible Clinton running mates audition with attacks on Trump and defenses of their views on trade
119 | Christi Parsons
120 | http://www.latimes.com/la-na-trailguide-updates-possible-clinton-running-mates-audition-1467553920-htmlstory.html
121 |
124 | Sun, 3 Jul 2016 09:06:00 PDT
125 |
126 |
127 |
128 | Scott Kazmir gets a first-inning adjustment
129 | Jesse Dougherty
130 | http://www.latimes.com/la-sp-scott-kazmir-first-inning-20160702-snap-story.html
131 | Scott Kazmir’s up-and-down season has carried one glaring theme: The first inning is his toughest obstacle.
Heading into his start against the Rockies on Saturday, Kazmir had a 9.00 ERA in the first inning of his 16 starts. Opponents were hitting a healthy .342 against him in the first. More than...
133 | ]]>
134 | Sun, 3 Jul 2016 09:00:00 PDT
135 |
136 |
137 |
138 | Former campaign bus rolls into Los Angeles as anti-Trump protest art
139 | Javier Panzar
140 | http://www.latimes.com/politics/la-pol-ca-donald-trump-bus-iowa-california-20160702-snap-story.html
141 |
144 | Sun, 3 Jul 2016 08:43:00 PDT
145 |
146 |
147 |
148 | Woman saves pet cockatiel, but home burned in fast-moving brush fire
149 | Howard Blume and Shelby Grad
150 | http://www.latimes.com/la-me-sb-fire-update-20160703-snap-story.html
151 | The fire seemed to come out of nowhere and spread rapidly Saturday in a San Bernardino neighborhood.
Resident Martha Hall told the San Bernardino Sun that she saw the flames rushing up the hill toward her home. She ran into her house, grabbed her pet cockatiel and fled. As she was leaving, she...
153 | ]]>
154 | Sun, 3 Jul 2016 08:33:00 PDT
155 |
156 |
157 |
158 | In Colorado, conservatives grapple with the Trump conundrum
159 | Melanie Mason
160 | http://www.latimes.com/la-na-pol-trump-colorado-conservatives-20160703-snap-story.html
161 | To understand the dilemma Colorado Republicans wrestled with at a conservative gathering this weekend, one only had to look at the range of speakers, whose positions on Donald Trump ran the gamut from enthusiastic support to vehement opposition.
Trump himself came to Colorado for the Western Conservative...
163 | ]]>
164 | Sun, 3 Jul 2016 08:15:00 PDT
165 |
166 |
167 |
168 | Porter Ranch's future after massive gas leak is in the eye of the beholder
169 | Alice Walton
170 | http://www.latimes.com/la-me-porter-ranch-20160629-snap-story.html
171 | In the hills above the 118 freeway, mansions are being built. Restaurants and grocery stores are packed. Cyclists pedal up and down wide-open streets.
On the surface, the community of Porter Ranch is returning to normal four months after the largest methane leak in American history was capped in...
173 | ]]>
174 | Sun, 3 Jul 2016 08:00:00 PDT
175 |
176 |
177 |
178 | Web Buzz: With the Lola app, personal travel advice and service are a quick text away
179 | Jen Leo
180 | http://www.latimes.com/la-tr-webbuzz-20160624-snap-story.html
181 | Need immediate advice about a flight or hotel? Here’s an instant messaging app that connects you to a helpful online concierge.
What it does: Connects travelers with travel agents who can find the best options for you based on your preferences, including favorite airlines, hotel...
183 | ]]>
184 | Sun, 3 Jul 2016 08:00:00 PDT
185 |
186 |
187 | 'Deer Hunter,' 'Heaven's Gate' director Michael Cimino dies at 77; the film community reacts
188 | Deborah Vankin
189 | http://www.latimes.com/la-et-mn-michael-cimino-20160702-snap-htmlstory.html
190 |
193 | Sun, 3 Jul 2016 07:38:00 PDT
194 |
195 |
196 |
197 | Inside Donald Trump's secret smear campaign against a tribal casino
198 | Joseph Tanfani
199 | http://www.latimes.com/politics/la-na-pol-trump-anti-indian-campaign-20160630-snap-story.html
200 |
203 | Sun, 3 Jul 2016 07:13:00 PDT
204 |
205 |
206 |
207 | Letters: To tip or not to tip, plus spritzing while driving
208 | http://www.latimes.com/la-tr-letters-20160626-snap-story.html
209 | I just returned from two weeks in England: one week in Bath, in the Cotswolds, and one week in London. I tipped everywhere I would in L.A., and the recipients were very appreciative [“Tips on Tipping,” On the Spot by Catharine Hamm, June 26].
Upon leaving for London, I took a taxi to the train...
211 | ]]>
212 | Sun, 3 Jul 2016 06:30:00 PDT
213 |
214 |
215 |
216 | Tesla and Google are both driving toward autonomous vehicles. Which company is taking the better route?
217 | Tracey Lien
218 | http://www.latimes.com/la-fi-hy-tesla-google-20160701-snap-story.html
219 | Google and Tesla agree autonomous vehicles will make streets safer, and both are racing toward a driverless future. But when Google tested its self-driving car prototype on employees a few years ago, it noticed something that would take it down a different path from Tesla.
Once behind the wheel...
221 | ]]>
222 | Sun, 3 Jul 2016 06:00:00 PDT
223 |
224 |
225 |
226 | CEOs are getting more political, but consumers aren't buying it
227 | Jena McGregor
228 | http://www.latimes.com/la-fi-on-leadership-ceo-activism-20160630-snap-story.html
229 | Starbucks Chief Executive Howard Schultz has spoken out on gun control, race relations and the "cynicism, despair, division, exclusion, fear and yes -- indifference" in America today.
Facebook founder and CEO Mark Zuckerberg said at a developer conference this year that "I hear fearful voices calling...
231 | ]]>
232 | Sun, 3 Jul 2016 06:00:00 PDT
233 |
234 |
235 |
236 | Feedback: Why type when you can write?
237 | http://www.latimes.com/la-ca-0703-feedback-20160627-snap-story.html
238 | Buried within Laila Lalami’s entertaining essay [“The Power of Procrastination,” June 24] are the words “the blank screen.” Therein lies her problem. For years, well-intentioned friends have urged me to use a computer instead of writing longhand on yellow lined paper, because, they say, “it is...
240 | ]]>
241 | Sun, 3 Jul 2016 06:00:00 PDT
242 |
243 |
244 |
245 | Learn all about exploring Yosemite National Park from the experts at REI
246 | http://www.latimes.com/la-tr-films-20160620-snap-story.html
247 | YOSEMITE
Workshop
REI experts will share tips on exploring Yosemite National Park.
When, where: 7 p.m. Thursday at the REI store in Arcadia, 214 N. Santa Anita Ave.
Admission, info: Free. (626) 447-1062
PATAGONIA
Presentation
Explorer and mountain guide Tad McCrea will share his experiences and...
249 | ]]>
250 | Sun, 3 Jul 2016 06:00:00 PDT
251 |
252 |
253 |
254 |
--------------------------------------------------------------------------------
/cache/feed/spiegel.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SPIEGEL ONLINE - Schlagzeilen
6 | http://www.spiegel.de
7 | Deutschlands führende Nachrichtenseite. Alles Wichtige aus Politik, Wirtschaft, Sport, Kultur, Wissenschaft, Technik und mehr.
8 | de
9 | Thu, 05 Nov 2015 23:50:24 +0100
10 | Thu, 05 Nov 2015 23:50:24 +0100
11 |
12 | SPIEGEL ONLINE
13 | http://www.spiegel.de
14 | http://www.spiegel.de/static/sys/logo_120x61.gif
15 |
16 |
17 | Unglück in Bayern: Zug erfasst Schwertransporter - mehrere Tote
18 | http://www.spiegel.de/panorama/gesellschaft/bayern-zug-erfasst-schwertransporter-auf-bahnuebergang-a-1061387.html#ref=rss
19 | Schweres Unglück in der Oberpfalz: Ein Lkw und ein Zug sind auf einem Bahnübergang kollidiert, beide fingen Feuer. Laut Polizei gab es Tote und Verletzte.
20 | Panorama
21 | Thu, 05 Nov 2015 23:42:00 +0100
22 | http://www.spiegel.de/panorama/gesellschaft/bayern-zug-erfasst-schwertransporter-auf-bahnuebergang-a-1061387.html
23 |
24 |
25 |
26 | US-Republikaner: Trump und Carson bekommen Schutz vom Secret Service
27 | http://www.spiegel.de/politik/ausland/usa-secret-service-bewacht-trump-und-carson-von-den-republikanern-a-1061385.html#ref=rss
28 | Moderne Insignien der Macht für Donald Trump und Ben Carson: Die beiden Präsidentschaftskandidaten der US-Republikaner sollen nun rund um die Uhr vom Secret Service beschützt werden - auch, weil sie in Umfragen vorne liegen.
29 | Politik
30 | Thu, 05 Nov 2015 23:23:51 +0100
31 | http://www.spiegel.de/politik/ausland/usa-secret-service-bewacht-trump-und-carson-von-den-republikanern-a-1061385.html
32 | Moderne Insignien der Macht für Donald Trump und Ben Carson: Die beiden Präsidentschaftskandidaten der US-Republikaner sollen nun rund um die Uhr vom Secret Service beschützt werden - auch, weil sie in Umfragen vorne liegen.]]>
33 |
34 |
35 |
36 | Europa League: Bobadilla lässt Augsburg jubeln
37 | http://www.spiegel.de/sport/fussball/europa-league-fc-augsburg-gewinnt-gegen-az-alkmaar-deutlich-a-1061379.html#ref=rss
38 | Chance gewahrt: Der FC Augsburg hat nach einem deutlichen Sieg gegen AZ Alkmaar gute Aussichten auf das Erreichen der K.o.-Phase. Beim Bundesligisten traf ein Spieler dreifach.
39 | Sport
40 | Thu, 05 Nov 2015 22:59:00 +0100
41 | http://www.spiegel.de/sport/fussball/europa-league-fc-augsburg-gewinnt-gegen-az-alkmaar-deutlich-a-1061379.html
42 | Chance gewahrt: Der FC Augsburg hat nach einem deutlichen Sieg gegen AZ Alkmaar gute Aussichten auf das Erreichen der K.o.-Phase. Beim Bundesligisten traf ein Spieler dreifach.]]>
43 |
44 |
45 |
46 | Remis gegen Sparta Prag: Schalkes Krise geht in der Europa League weiter
47 | http://www.spiegel.de/sport/fussball/fc-schalke-spielt-in-der-europa-league-bei-sparta-prag-remis-a-1061382.html#ref=rss
48 | Der FC Schalke geht angeschlagen ins Derby gegen Borussia Dortmund. In der Europa League reichte es bei Sparta Prag nur zu einem Unentschieden, vor allem die verletzten Abwehrspieler bereiten Trainer André Breitenreiter Sorgen.
49 | Sport
50 | Thu, 05 Nov 2015 22:58:00 +0100
51 | http://www.spiegel.de/sport/fussball/fc-schalke-spielt-in-der-europa-league-bei-sparta-prag-remis-a-1061382.html
52 | Der FC Schalke geht angeschlagen ins Derby gegen Borussia Dortmund. In der Europa League reichte es bei Sparta Prag nur zu einem Unentschieden, vor allem die verletzten Abwehrspieler bereiten Trainer André Breitenreiter Sorgen.]]>
53 |
54 |
55 |
56 | Koalitionsstreit in der Flüchtlingskrise: Erledigt
57 | http://www.spiegel.de/politik/deutschland/fluechtlingskrise-merkel-seehofer-gabriel-finden-kompromiss-a-1061380.html#ref=rss
58 | Transitzonen + Einreisezentren = Aufnahme-Einrichtungen. Das ist der Kompromiss der Großen Koalition. CSU-Chef Seehofer erscheint als Verlierer des Gipfeltreffens. Ist er das wirklich?
59 | Politik
60 | Thu, 05 Nov 2015 22:18:00 +0100
61 | http://www.spiegel.de/politik/deutschland/fluechtlingskrise-merkel-seehofer-gabriel-finden-kompromiss-a-1061380.html
62 | Transitzonen + Einreisezentren = Aufnahme-Einrichtungen. Das ist der Kompromiss der Großen Koalition. CSU-Chef Seehofer erscheint als Verlierer des Gipfeltreffens. Ist er das wirklich?]]>
63 |
64 |
65 |
66 | Schwedischer Minister zu Flüchtlingen: "Bleibt in Deutschland"
67 | http://www.spiegel.de/politik/ausland/schweden-kann-fluechtlinge-laut-minister-nicht-mehr-unterbringen-a-1061378.html#ref=rss
68 | Schweden ist für seine liberale Asypolitik bekannt, doch jetzt will der Migrationsminister Flüchtlinge fernhalten - das Land habe nicht genug Unterkünfte. Morgan Johansson hat Asylsuchende aufgefordert, nach Deutschland zurückzukehren.
69 | Politik
70 | Thu, 05 Nov 2015 21:48:00 +0100
71 | http://www.spiegel.de/politik/ausland/schweden-kann-fluechtlinge-laut-minister-nicht-mehr-unterbringen-a-1061378.html
72 | Schweden ist für seine liberale Asypolitik bekannt, doch jetzt will der Migrationsminister Flüchtlinge fernhalten - das Land habe nicht genug Unterkünfte. Morgan Johansson hat Asylsuchende aufgefordert, nach Deutschland zurückzukehren.]]>
73 |
74 |
75 |
76 | Pope-Pop: Franziskus veröffentlicht Rockalbum
77 | http://www.spiegel.de/panorama/gesellschaft/papst-cd-franziskus-veroeffentlicht-rock-album-a-1061376.html#ref=rss
78 | Röhrende Bässe, E-Gitarren-Soli und dazu die Predigten von Franziskus: Über die musikalische Qualität des ersten Rock-Pop-Albums des Papstes lässt sich streiten, doch das Anliegen ist hehr.
79 | Panorama
80 | Thu, 05 Nov 2015 21:38:00 +0100
81 | http://www.spiegel.de/panorama/gesellschaft/papst-cd-franziskus-veroeffentlicht-rock-album-a-1061376.html
82 | Röhrende Bässe, E-Gitarren-Soli und dazu die Predigten von Franziskus: Über die musikalische Qualität des ersten Rock-Pop-Albums des Papstes lässt sich streiten, doch das Anliegen ist hehr.]]>
83 |
84 |
85 |
86 | Europa League: Klopp siegt mit Liverpool auch international
87 | http://www.spiegel.de/sport/fussball/juergen-klopp-siegt-mit-liverpool-in-der-europa-league-a-1061373.html#ref=rss
88 | Mehr als 3000 Kilometer Anreise und frostige Temperaturen haben sich gelohnt: Jürgen Klopp hat mit Liverpool in Kasan seinen dritten Sieg in Folge gefeiert. Neapel steht schon in der Zwischenrunde.
89 | Sport
90 | Thu, 05 Nov 2015 20:56:00 +0100
91 | http://www.spiegel.de/sport/fussball/juergen-klopp-siegt-mit-liverpool-in-der-europa-league-a-1061373.html
92 | Mehr als 3000 Kilometer Anreise und frostige Temperaturen haben sich gelohnt: Jürgen Klopp hat mit Liverpool in Kasan seinen dritten Sieg in Folge gefeiert. Neapel steht schon in der Zwischenrunde.]]>
93 |
94 |
95 |
96 | Europa League: Dortmund siegt und schafft es vorzeitig in die Zwischenrunde
97 | http://www.spiegel.de/sport/fussball/europa-league-borussia-dortmund-bezwingt-fk-qaebaelae-a-1061372.html#ref=rss
98 | Borussia Dortmund ist dank eines überzeugenden Sieges gegen FK Qäbälä frühzeitig in die K.o-Runde der Europa League eingezogen. Der BVB konnte es sich dabei sogar erlauben, einige Leistungsträger zu schonen.
99 | Sport
100 | Thu, 05 Nov 2015 20:53:22 +0100
101 | http://www.spiegel.de/sport/fussball/europa-league-borussia-dortmund-bezwingt-fk-qaebaelae-a-1061372.html
102 | Borussia Dortmund ist dank eines überzeugenden Sieges gegen FK Qäbälä frühzeitig in die K.o-Runde der Europa League eingezogen. Der BVB konnte es sich dabei sogar erlauben, einige Leistungsträger zu schonen.]]>
103 |
104 |
105 |
106 | Nasa-Daten zeigen: Sonneneruptionen reißen Mars-Atmosphäre weg
107 | http://www.spiegel.de/wissenschaft/weltall/sonneneruptionen-reissen-mars-atmosphaere-weg-a-1061367.html#ref=rss
108 | Verglichen mit der Erde hat der Mars eine extrem dünne Atmosphäre. Schuld daran sind wohl massive Sonnenstürme. Darauf deuten Messungen der Nasa-Sonde "Maven" hin.
109 | Wissenschaft
110 | Thu, 05 Nov 2015 20:04:00 +0100
111 | http://www.spiegel.de/wissenschaft/weltall/sonneneruptionen-reissen-mars-atmosphaere-weg-a-1061367.html
112 | Verglichen mit der Erde hat der Mars eine extrem dünne Atmosphäre. Schuld daran sind wohl massive Sonnenstürme. Darauf deuten Messungen der Nasa-Sonde "Maven" hin.]]>
113 |
114 |
115 |
116 | James-Bond-Quiz: Lizenz zum Danebenliegen
117 | http://www.spiegel.de/kultur/kino/james-bond-quiz-haben-sie-das-zeug-zum-doppel-null-agenten-a-1061142.html#ref=rss
118 | Sie wissen alles über James Bond? Kennen alle Girls, alle Autos, alle Action-Szenen? Bestimmt nicht! Das schwerste Bond-Quiz, zu dem SPIEGEL ONLINE imstande ist, wird Sie eines Besseren belehren.
119 | Kultur
120 | Thu, 05 Nov 2015 19:40:30 +0100
121 | http://www.spiegel.de/kultur/kino/james-bond-quiz-haben-sie-das-zeug-zum-doppel-null-agenten-a-1061142.html
122 | Sie wissen alles über James Bond? Kennen alle Girls, alle Autos, alle Action-Szenen? Bestimmt nicht! Das schwerste Bond-Quiz, zu dem SPIEGEL ONLINE imstande ist, wird Sie eines Besseren belehren.]]>
123 |
124 |
125 |
126 | James-Bond-Quiz: Haben Sie das Zeug zum Doppel-Null-Agenten?
127 | http://www.spiegel.de/quiztool/quiztool-64516.html#ref=rss
128 | Mit welchem Bond-Girl war Womanizer James Bond verheiratet? Wie tötete 007 den Bösewicht Sanchez in "Octopussy" und welcher der bislang vierundzwanzig Streifen ist kein "richtiger" Bond? Testen Sie Ihr Geheimagenten-Wissen im Quiz.
129 | Kultur
130 | Thu, 05 Nov 2015 19:39:31 +0100
131 | http://www.spiegel.de/quiztool/quiztool-64516.html
132 |
133 |
134 |
135 | Drama "El Club": Priester ohne Reue
136 | http://www.spiegel.de/kultur/kino/el-club-filmkritik-die-suenden-der-seelsorger-a-1060686.html#ref=rss
137 | Vier suspendierte Priester leben an der Nordküste Chiles, schuldig und isoliert. Nach einer Gewalttat zwingt ein Ermittler sie dazu, sich ihrer eigenen Vergangenheit zu stellen. "El Club" ist packender Mystery-Thriller und Abrechnung zugleich.
138 | Kultur
139 | Thu, 05 Nov 2015 19:25:00 +0100
140 | http://www.spiegel.de/kultur/kino/el-club-filmkritik-die-suenden-der-seelsorger-a-1060686.html
141 |
142 |
143 |
144 | Berühmter Historiker: Hans Mommsen ist tot
145 | http://www.spiegel.de/kultur/gesellschaft/hans-mommsen-ist-tot-a-1061374.html#ref=rss
146 | Er zählte zu den streitbarsten Historikern der Nachkriegszeit: Hans Mommsen prägte die Forschung über die NS-Zeit. Jetzt ist er an seinem 85. Geburtstag in Bayern gestorben.
147 | Kultur
148 | Thu, 05 Nov 2015 19:24:00 +0100
149 | http://www.spiegel.de/kultur/gesellschaft/hans-mommsen-ist-tot-a-1061374.html
150 | Er zählte zu den streitbarsten Historikern der Nachkriegszeit: Hans Mommsen prägte die Forschung über die NS-Zeit. Jetzt ist er an seinem 85. Geburtstag in Bayern gestorben.]]>
151 |
152 |
153 |
154 | Mafia-Prozess in Italien: "Die Stadträte müssen unseren Befehlen folgen"
155 | http://www.spiegel.de/panorama/justiz/rom-mafia-capitale-um-massimo-carminati-vor-gericht-a-1061316.html#ref=rss
156 | In Italien hat ein Riesenprozess gegen die Hauptstadtmafia begonnen: 250 Zeugen sollen gehört werden, 46 Angeklagte stehen vor Gericht. Im Mittelpunkt steht Massimo "der Schwarze" Carminati, selbsternannter König von Rom.
157 | Panorama
158 | Thu, 05 Nov 2015 19:00:00 +0100
159 | http://www.spiegel.de/panorama/justiz/rom-mafia-capitale-um-massimo-carminati-vor-gericht-a-1061316.html
160 | In Italien hat ein Riesenprozess gegen die Hauptstadtmafia begonnen: 250 Zeugen sollen gehört werden, 46 Angeklagte stehen vor Gericht. Im Mittelpunkt steht Massimo "der Schwarze" Carminati, selbsternannter König von Rom.]]>
161 |
162 |
163 |
164 | Push-Nachrichten-App Notify: Facebooks erdrückende Umarmung
165 | http://www.spiegel.de/netzwelt/netzpolitik/facebook-notify-news-app-soll-push-nachrichten-schicken-a-1061368.html#ref=rss
166 | Medienberichten zufolge will Facebook eine Nachrichten-App auf den Markt bringen. Push-Meldungen diverser Medien sollen darin nach den Wünschen der Nutzer zusammengestellt werden. Das passt zu Facebooks Umarmungsstrategie gegenüber Medien.
167 | Netzwelt
168 | Thu, 05 Nov 2015 18:43:00 +0100
169 | http://www.spiegel.de/netzwelt/netzpolitik/facebook-notify-news-app-soll-push-nachrichten-schicken-a-1061368.html
170 | Medienberichten zufolge will Facebook eine Nachrichten-App auf den Markt bringen. Push-Meldungen diverser Medien sollen darin nach den Wünschen der Nutzer zusammengestellt werden. Das passt zu Facebooks Umarmungsstrategie gegenüber Medien. ]]>
171 |
172 |
173 |
174 | Emma Watson trifft Malala Yousafzai: Endlich Feministin
175 | http://www.spiegel.de/panorama/leute/emma-watson-und-malala-yousafzai-ueber-frauenrechte-a-1061346.html#ref=rss
176 | Malala Yousafzai und Emma Watson setzen sich beide für die Rechte der Frauen ein. Als Feministin wollte sich Yousafzai jedoch nie bezeichnen - bis sie von Watson überzeugt wurde.
177 | Panorama
178 | Thu, 05 Nov 2015 18:41:00 +0100
179 | http://www.spiegel.de/panorama/leute/emma-watson-und-malala-yousafzai-ueber-frauenrechte-a-1061346.html
180 |
181 |
182 |
183 | Nach Hackerangriff: Abgeordnete müssen jetzt Passwörter mit mindestens acht Zeichen verwenden
184 | http://www.spiegel.de/politik/deutschland/hackerangriff-bundestag-ruestet-ein-bisschen-auf-a-1061332.html#ref=rss
185 | Was hat der Bundestag aus dem schweren Hackerangriff gelernt? Das Parlament schränkt jetzt sogar das Internet für Abgeordnete ein - die Angreifer sind weiterhin unentdeckt.
186 | Politik
187 | Thu, 05 Nov 2015 18:36:00 +0100
188 | http://www.spiegel.de/politik/deutschland/hackerangriff-bundestag-ruestet-ein-bisschen-auf-a-1061332.html
189 | Was hat der Bundestag aus dem schweren Hackerangriff gelernt? Das Parlament schränkt jetzt sogar das Internet für Abgeordnete ein - die Angreifer sind weiterhin unentdeckt. ]]>
190 |
191 |
192 |
193 | Kraftfahrt-Bundesamt: Machtlose Aufseher
194 | http://www.spiegel.de/wirtschaft/soziales/kraftfahrt-bundesamt-die-ohnmaechtigen-aufseher-a-1061338.html#ref=rss
195 | Die Abgasaffäre bei Volkswagen hat auch den Ruf des Kraftfahrt-Bundesamts schwer beschädigt. Doch die Vorwürfe zielen in die falsche Richtung. Denn die Ansagen kommen aus Berlin.
196 | Wirtschaft
197 | Thu, 05 Nov 2015 18:16:00 +0100
198 | http://www.spiegel.de/wirtschaft/soziales/kraftfahrt-bundesamt-die-ohnmaechtigen-aufseher-a-1061338.html
199 |
200 |
201 |
202 | Mutmaßlicher NSU-Helfer: Die Handy-Kontakte des André E.
203 | http://www.spiegel.de/panorama/nsu-prozess-die-handy-kontakte-des-andre-e-a-1020286.html#ref=rss
204 | Die große Aufmerksamkeit für Beate Zschäpe kann dem Mitangeklagten André E. nur recht sein: Er unterstützte den NSU laut Anklage über Jahre. Seine Handydaten zeigen, wie viele Kontakte er zu mutmaßlichen Kriminellen pflegte.
205 | Panorama
206 | Thu, 05 Nov 2015 18:04:00 +0100
207 | http://www.spiegel.de/panorama/nsu-prozess-die-handy-kontakte-des-andre-e-a-1020286.html
208 | Die große Aufmerksamkeit für Beate Zschäpe kann dem Mitangeklagten André E. nur recht sein: Er unterstützte den NSU laut Anklage über Jahre. Seine Handydaten zeigen, wie viele Kontakte er zu mutmaßlichen Kriminellen pflegte.]]>
209 |
210 |
211 |
212 | Gipfel in Berlin: Koalition einigt sich auf Registrierzentren für Flüchtlinge
213 | http://www.spiegel.de/politik/deutschland/fluechtlinge-koalition-einigt-sich-auf-registrierzentren-a-1061370.html#ref=rss
214 | Keine Transitzonen an der Grenze, dafür spezielle Aufnahme-Einrichtungen für Flüchtlinge aus sicheren Herkunftsländern: Auf diesen Kompromiss haben sich die Spitzen der Großen Koalition bei ihrem Flüchtlingsgipfel geeinigt.
215 | Politik
216 | Thu, 05 Nov 2015 17:59:00 +0100
217 | http://www.spiegel.de/politik/deutschland/fluechtlinge-koalition-einigt-sich-auf-registrierzentren-a-1061370.html
218 | Keine Transitzonen an der Grenze, dafür spezielle Aufnahme-Einrichtungen für Flüchtlinge aus sicheren Herkunftsländern: Auf diesen Kompromiss haben sich die Spitzen der Großen Koalition bei ihrem Flüchtlingsgipfel geeinigt.]]>
219 |
220 |
221 |
222 | Vorratsdatenspeicherung: Thüringen will im Bundesrat Vermittlungsausschuss anrufen
223 | http://www.spiegel.de/netzwelt/web/vorratsdatenspeicherung-thueringen-will-vermittlungsausschuss-anrufen-a-1061363.html#ref=rss
224 | Am Freitag ist die umstrittene Vorratsdatenspeicherung Thema im Bundesrat. Thüringens Justizminister hat bereits angekündigt, wegen Datenschutzbedenken den Vermittlungsausschuss anrufen zu wollen. Seine Chancen stehen aber schlecht.
225 | Netzwelt
226 | Thu, 05 Nov 2015 17:49:00 +0100
227 | http://www.spiegel.de/netzwelt/web/vorratsdatenspeicherung-thueringen-will-vermittlungsausschuss-anrufen-a-1061363.html
228 |
229 |
230 |
231 | Teuerste H&M-Kollektion: Elitäres für die Massen
232 | http://www.spiegel.de/stil/balmain-entwirft-fuer-h-m-elitaeres-fuer-die-massen-a-1060771.html#ref=rss
233 | Es hat mit Karl Lagerfeld funktioniert und mit Stella McCartney: Top-Designer entwerfen für H&M, Kunden stürmen die Läden - und zahlen ein Vielfaches der üblichen Preise. Die neue Kollektion kommt von Olivier Rousteing und ist so teuer wie keine zuvor. Zu teuer? Stimmen Sie ab!
234 | Stil
235 | Thu, 05 Nov 2015 17:47:00 +0100
236 | http://www.spiegel.de/stil/balmain-entwirft-fuer-h-m-elitaeres-fuer-die-massen-a-1060771.html
237 |
238 |
239 |
240 | Flüchtlingsfamilien: Kinder ohne Papiere dürfen zur Schule
241 | http://www.spiegel.de/schulspiegel/kinder-ohne-aufenthaltsrecht-duerfen-zur-schule-a-1060431.html#ref=rss
242 | Alle Kinder haben ein Recht auf Bildung - auch wenn sie nicht alle Papiere haben, die Deutschland vorschreibt. Doch nehmen Schulen solche Kinder überhaupt auf? Und müssen sie das an die Behörden melden? Die Antworten auf die wichtigsten Fragen.
243 | SchulSPIEGEL
244 | Thu, 05 Nov 2015 17:41:00 +0100
245 | http://www.spiegel.de/schulspiegel/kinder-ohne-aufenthaltsrecht-duerfen-zur-schule-a-1060431.html
246 | Alle Kinder haben ein Recht auf Bildung - auch wenn sie nicht alle Papiere haben, die Deutschland vorschreibt. Doch nehmen Schulen solche Kinder überhaupt auf? Und müssen sie das an die Behörden melden? Die Antworten auf die wichtigsten Fragen.]]>
247 |
248 |
249 |
250 | Afghanistan: Ärzte ohne Grenzen wirft USA vorsätzlichen Angriff auf Klinik vor
251 | http://www.spiegel.de/politik/ausland/aerzte-ohne-grenzen-werfen-usa-vorsaetzlichen-angriff-auf-klinik-in-kunduz-vor-a-1061362.html#ref=rss
252 | Bei einem US-Luftangriff auf ein Krankenhaus in Kunduz wurden vor einem Monat 30 Menschen getötet. Die Hilfsorganisation Ärzte ohne Grenzen bezweifelt, dass die Armee aus Versehen handelte.
253 | Politik
254 | Thu, 05 Nov 2015 17:25:00 +0100
255 | http://www.spiegel.de/politik/ausland/aerzte-ohne-grenzen-werfen-usa-vorsaetzlichen-angriff-auf-klinik-in-kunduz-vor-a-1061362.html
256 | Bei einem US-Luftangriff auf ein Krankenhaus in Kunduz wurden vor einem Monat 30 Menschen getötet. Die Hilfsorganisation Ärzte ohne Grenzen bezweifelt, dass die Armee aus Versehen handelte. ]]>
257 |
258 |
259 |
260 | Elendspanorama "Vorbereitung auf das nächste Leben": Amerika ist erledigt
261 | http://www.spiegel.de/kultur/literatur/vorbereitung-auf-das-naechste-leben-von-atticus-lish-rezension-a-1060858.html#ref=rss
262 | Eine Geschichte vom unteren Rand der Gesellschaft: Atticus Lish gelang mit seinem Debütroman "Vorbereitung auf das nächste Leben" ein Überraschungshit in den USA, der das Pathos zurück in die Gegenwartsliteratur bringt.
263 | Kultur
264 | Thu, 05 Nov 2015 17:17:00 +0100
265 | http://www.spiegel.de/kultur/literatur/vorbereitung-auf-das-naechste-leben-von-atticus-lish-rezension-a-1060858.html
266 |
267 |
268 |
269 | Mehrtägiger Streik: Was Lufthansa-Passagiere jetzt wissen müssen
270 | http://www.spiegel.de/reise/aktuell/lufthansa-streik-was-passagiere-wissen-muessen-a-1061342.html#ref=rss
271 | Lufthansa-Kunden müssen sich auf einen mehrtägigen Streik mit vielen Flugausfällen einrichten. Die Flugbegleiter wollen aber nicht vor Freitagmittag mit ihrem Ausstand beginnen. Hier finden Sie Antworten auf die wichtigsten Fragen.
272 | Reise
273 | Thu, 05 Nov 2015 17:08:00 +0100
274 | http://www.spiegel.de/reise/aktuell/lufthansa-streik-was-passagiere-wissen-muessen-a-1061342.html
275 | Lufthansa-Kunden müssen sich auf einen mehrtägigen Streik mit vielen Flugausfällen einrichten. Die Flugbegleiter wollen aber nicht vor Freitagmittag mit ihrem Ausstand beginnen. Hier finden Sie Antworten auf die wichtigsten Fragen.]]>
276 |
277 |
278 |
279 | Verhandlungen geplatzt: Lufthansa-Streik startet am Freitag
280 | http://www.spiegel.de/wirtschaft/unternehmen/lufthansa-streik-am-freitag-a-1061357.html#ref=rss
281 | Auch der letzte Einigungsversuch ist gescheitert: Die Flugbegleiter der Lufthansa streiken ab Freitag - mit Rücksicht auf die Kunden aber erst ab 12 Uhr.
282 | Wirtschaft
283 | Thu, 05 Nov 2015 17:04:00 +0100
284 | http://www.spiegel.de/wirtschaft/unternehmen/lufthansa-streik-am-freitag-a-1061357.html
285 |
286 |
287 |
288 | 1000 Kilometer entfernt: US-Ermittler entdecken vermissten Jungen nach 13 Jahren
289 | http://www.spiegel.de/panorama/ohio-vermisster-junge-in-den-usa-nach-13-jahren-aufgetaucht-a-1061350.html#ref=rss
290 | Mit fünf Jahren verschwand ein Junge aus Alabama. Die Mutter vermutete, der Vater habe den Jungen entführt. Jahre später haben ihn nun Ermittler in Ohio entdeckt - durch einen Zufall.
291 | Panorama
292 | Thu, 05 Nov 2015 17:00:00 +0100
293 | http://www.spiegel.de/panorama/ohio-vermisster-junge-in-den-usa-nach-13-jahren-aufgetaucht-a-1061350.html
294 |
295 |
296 |
297 | Pressekompass: Sterbehilfevereine legal oder illegal? Das sagen die Medien
298 | http://www.spiegel.de/gesundheit/diagnose/pressekompass-zur-sterbehilfe-debatte-im-bundestag-a-1061351.html#ref=rss
299 | Totales Verbot oder liberale Freigabe - die vier Gesetzentwürfe zur Sterbehilfe könnten unterschiedlicher nicht sein. Wie entscheidet der Bundestag am Freitag? Der Pressekompass zeigt Meinungstrends der Medien.
300 | Gesundheit
301 | Thu, 05 Nov 2015 16:59:00 +0100
302 | http://www.spiegel.de/gesundheit/diagnose/pressekompass-zur-sterbehilfe-debatte-im-bundestag-a-1061351.html
303 | Totales Verbot oder liberale Freigabe - die vier Gesetzentwürfe zur Sterbehilfe könnten unterschiedlicher nicht sein. Wie entscheidet der Bundestag am Freitag? Der Pressekompass zeigt Meinungstrends der Medien.]]>
304 |
305 |
306 |
307 |
308 |
--------------------------------------------------------------------------------
/config/config.exs:
--------------------------------------------------------------------------------
1 | # This file is responsible for configuring your application
2 | # and its dependencies with the aid of the Mix.Config module.
3 | use Mix.Config
4 |
5 | # This configuration is loaded before any dependency and is restricted
6 | # to this project. If another project depends on this project, this
7 | # file won't be loaded nor affect the parent project. For this reason,
8 | # if you want to provide default values for your application for
9 | # third-party users, it should be done in your "mix.exs" file.
10 |
11 | # You can configure your application as:
12 | #
13 | # config :scrape, key: :value
14 | #
15 | # and access this configuration in your application as:
16 | #
17 | # Application.get_env(:scrape, :key)
18 | #
19 | # You can also configure a third-party app:
20 | #
21 | # config :logger, level: :info
22 | #
23 |
24 | # It is also possible to import configuration files, relative to this
25 | # directory. For example, you can emulate configuration per environment
26 | # by uncommenting the line below and defining dev.exs, test.exs and such.
27 | # Configuration from the imported file will override the ones defined
28 | # here (which is why it is important to import them last).
29 | #
30 | # import_config "#{Mix.env()}.exs"
31 |
--------------------------------------------------------------------------------
/lib/scrape.ex:
--------------------------------------------------------------------------------
1 | defmodule Scrape do
2 | @moduledoc """
3 | Elixir Toolkit for extracting meaningful structured data out of
4 | common web resources.
5 |
6 | This process is often called "web-scraping". Actually, the normalization
7 | and transformation of data into a well-known structured form is also
8 | known as "data engineering", which in turn is the prerequisite for most
9 | data-science/machine-learning/... algorithms in the wild.
10 |
11 | Currently Scrape supports 3 types of common web data:
12 |
13 | * Feeds: RSS or Atom XML feeds
14 | * Domains: "root" pages of a web presence
15 | * Articles: "content" pages of a web presence
16 | """
17 |
18 | @doc """
19 | Given a valid url, return structured data of the content.
20 |
21 | This function is intended for "content" pages.
22 | """
23 |
24 | @spec article(String.t()) :: {:ok, map()} | {:error, any()}
25 | @spec article(String.t(), [{atom(), any()}]) :: {:ok, map()} | {:error, any()}
26 |
27 | def article(url, opts \\ []) do
28 | Scrape.Flow.Article.from_url(url, opts)
29 | end
30 |
31 | @doc """
32 | Same as `article/2` but will return the result directly or raise an
33 | error if the result is not `:ok`
34 | """
35 |
36 | def article!(url, opts \\ []) do
37 | {:ok, article} = Scrape.Flow.Article.from_url(url, opts)
38 | article
39 | end
40 |
41 | @doc """
42 | Given a valid url, return structured data of the domain.
43 |
44 | This function is intended for "root" pages of a web presence. The most
45 | important usecase for Scrape is to detect possible feeds for the domain.
46 | """
47 |
48 | @spec domain(String.t()) :: {:ok, map()} | {:error, any()}
49 | @spec domain(String.t(), [{atom(), any()}]) :: {:ok, map()} | {:error, any()}
50 |
51 | def domain(url, opts \\ []) do
52 | Scrape.Flow.Domain.from_url(url, opts)
53 | end
54 |
55 | @doc """
56 | Same as `domain/2` but will return the result directly or raise an
57 | error if the result is not `:ok`.
58 | """
59 |
60 | def domain!(url, opts \\ []) do
61 | {:ok, domain} = Scrape.Flow.Domain.from_url(url, opts)
62 | domain
63 | end
64 |
65 | @doc """
66 | Given a valid url, return structured data of the feed.
67 | """
68 |
69 | @spec feed(String.t()) :: {:ok, map()} | {:error, any()}
70 | @spec feed(String.t(), [{atom(), any()}]) :: {:ok, map()} | {:error, any()}
71 |
72 | def feed(url, opts \\ []) do
73 | Scrape.Flow.Feed.from_url(url, opts)
74 | end
75 |
76 | @doc """
77 | Same as `feed/2` but will return the result directly or raise an error
78 | if the result is not `:ok`.
79 | """
80 |
81 | def feed!(url, opts \\ []) do
82 | {:ok, feed} = Scrape.Flow.Feed.from_url(url, opts)
83 | feed
84 | end
85 | end
86 |
--------------------------------------------------------------------------------
/lib/scrape/application.ex:
--------------------------------------------------------------------------------
1 | defmodule Scrape.Application do
2 | # See https://hexdocs.pm/elixir/Application.html
3 | # for more information on OTP Applications
4 | @moduledoc false
5 |
6 | use Application
7 |
8 | def start(_type, _args) do
9 | # List all child processes to be supervised
10 | children = [
11 | # Starts a worker by calling: Scrape.Worker.start_link(arg)
12 | # {Scrape.Worker, arg}
13 | ]
14 |
15 | # See https://hexdocs.pm/elixir/Supervisor.html
16 | # for other strategies and supported options
17 | opts = [strategy: :one_for_one, name: Scrape.Supervisor]
18 | Supervisor.start_link(children, opts)
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/lib/scrape/flow.ex:
--------------------------------------------------------------------------------
1 | defmodule Scrape.Flow do
2 | @moduledoc """
3 | Logic Module for implementing linear data processing workflows.
4 |
5 | Uses a "token" approach to store/retrieve values and persists a pipeline
6 | state that can be halted at any time. In case that something goes wrong,
7 | the pipeline will be halted and an error object will be returned with the
8 | occured error. Therefore, the pipeline should never raise an actual exception.
9 | """
10 |
11 | @typedoc """
12 | Intermediate state object that holds everything relevant for the data
13 | processing work flow. `state` holds general processing information, `assigns`
14 | are the user-level data fields and `options` contains a keyword list for,
15 | well, configuration options.
16 | """
17 |
18 | @type flow :: %__MODULE__{
19 | state: %{
20 | halted: boolean(),
21 | error: nil | any()
22 | },
23 | assigns: map(),
24 | options: [{atom(), any()}]
25 | }
26 |
27 | defstruct(state: %{halted: false, error: nil}, assigns: %{}, options: [])
28 |
29 | @doc """
30 | Initiate a new data processing flow with optional configuration.
31 |
32 | NOTE: the options are currently not used but will be in upcoming versions.
33 |
34 | ## Example
35 | iex> Flow.start()
36 | %Flow{state: %{halted: false, error: nil}, assigns: %{}, options: []}
37 | """
38 |
39 | @spec start([{atom(), any()}]) :: flow
40 |
41 | def start(opts \\ []) do
42 | %__MODULE__{options: opts}
43 | end
44 |
45 | @doc """
46 | Declare a new value in the data flow.
47 |
48 | Will do nothing if the flow got halted previously. If a function is given,
49 | and it raises an exception, the pipeline will catch the error and transform
50 | into a halted state.
51 | """
52 |
53 | @spec assign(flow, [{atom(), any()}]) :: flow
54 |
55 | def assign(%__MODULE__{state: %{halted: true}} = flow, _) do
56 | flow
57 | end
58 |
59 | def assign(%__MODULE__{} = flow, [{k, v}]) when not is_function(v) do
60 | %{flow | assigns: Map.put(flow.assigns, k, v)}
61 | end
62 |
63 | def assign(%__MODULE__{} = flow, [{k, v}]) do
64 | try do
65 | %{flow | assigns: Map.put(flow.assigns, k, v.(flow.assigns))}
66 | rescue
67 | error -> %{flow | state: %{halted: true, error: {:assign, k, error}}}
68 | end
69 | end
70 |
71 | @doc """
72 | Select keys from the flow assigns and return a map with the chosen fields.
73 |
74 | Will result in an error object if the flow got halted previously.
75 | """
76 |
77 | @spec finish(flow, [atom()]) :: {:ok, map()} | {:error, any()}
78 |
79 | def finish(_flow, keys \\ [])
80 |
81 | def finish(%__MODULE__{state: %{halted: true, error: error}}, _) do
82 | {:error, error}
83 | end
84 |
85 | def finish(%__MODULE__{assigns: assigns}, []), do: {:ok, assigns}
86 |
87 | def finish(%__MODULE__{assigns: assigns}, keys) do
88 | {:ok, Map.take(assigns, keys)}
89 | end
90 | end
91 |
--------------------------------------------------------------------------------
/lib/scrape/flow/article.ex:
--------------------------------------------------------------------------------
1 | defmodule Scrape.Flow.Article do
2 | @moduledoc false
3 |
4 | alias Scrape.Flow
5 | alias Scrape.IR.HTML
6 | alias Scrape.IR.Text
7 |
8 | def from_url(url, opts \\ []) do
9 | Flow.start(opts)
10 | |> Flow.assign(url: url)
11 | |> Flow.assign(html: &Scrape.Source.HTTP.get!(&1[:url]))
12 | |> process_html()
13 | end
14 |
15 | def from_file(path, opts \\ []) do
16 | Flow.start(opts)
17 | |> Flow.assign(path: path)
18 | |> Flow.assign(html: &Scrape.Source.Disk.get!(&1[:path]))
19 | |> process_html()
20 | end
21 |
22 | def from_string(html, opts \\ []) do
23 | Flow.start(opts)
24 | |> Flow.assign(html: html)
25 | |> process_html()
26 | end
27 |
28 | defp process_html(%{assigns: %{html: nil}}), do: {:error, :html_invalid}
29 |
30 | defp process_html(%{assigns: %{html: ""}}), do: {:error, :html_invalid}
31 |
32 | defp process_html(flow) do
33 | flow
34 | |> Flow.assign(dom: &Floki.parse(&1[:html]))
35 | |> Flow.assign(title: &HTML.title(&1[:dom]))
36 | |> Flow.assign(image_url: &HTML.image_url(&1[:dom], &1[:url]))
37 | |> Flow.assign(readable_html: &HTML.simple(&1[:dom]))
38 | |> Flow.assign(text: fn %{html: html} -> HTML.content(html) || HTML.sentences(html) end)
39 | |> Flow.assign(language: &Text.detect_language(&1[:text]))
40 | |> Flow.assign(stems: &Text.semantic_keywords(&1[:text], 30, &1[:language]))
41 | |> Flow.assign(summary: &Text.extract_summary(&1[:text], &1[:stems], &1[:language]))
42 | |> Flow.finish([:url, :title, :text, :summary, :language, :stems, :image_url, :readable_html])
43 | end
44 | end
45 |
--------------------------------------------------------------------------------
/lib/scrape/flow/domain.ex:
--------------------------------------------------------------------------------
1 | defmodule Scrape.Flow.Domain do
2 | @moduledoc false
3 |
4 | alias Scrape.Flow
5 | alias Scrape.IR.HTML
6 |
7 | def from_url(url, opts \\ []) do
8 | Flow.start(opts)
9 | |> Flow.assign(url: url)
10 | |> Flow.assign(html: &Scrape.Source.HTTP.get!(&1[:url]))
11 | |> process_html()
12 | end
13 |
14 | def from_file(path, opts \\ []) do
15 | Flow.start(opts)
16 | |> Flow.assign(path: path)
17 | |> Flow.assign(html: &Scrape.Source.Disk.get!(&1[:path]))
18 | |> process_html()
19 | end
20 |
21 | def from_string(html, opts \\ []) do
22 | Flow.start(opts)
23 | |> Flow.assign(html: html)
24 | |> process_html()
25 | end
26 |
27 | defp process_html(%{assigns: %{html: nil}}), do: {:error, :html_invalid}
28 |
29 | defp process_html(%{assigns: %{html: ""}}), do: {:error, :html_invalid}
30 |
31 | defp process_html(flow) do
32 | flow
33 | |> Flow.assign(dom: &Floki.parse(&1[:html]))
34 | |> Flow.assign(title: &HTML.title(&1[:dom]))
35 | |> Flow.assign(description: &HTML.description(&1[:dom]))
36 | |> Flow.assign(icon_url: &HTML.icon_url(&1[:dom], &1[:url]))
37 | |> Flow.assign(feed_urls: &HTML.feed_urls(&1[:dom], &1[:url]))
38 | |> Flow.finish([:url, :title, :description, :icon_url, :feed_urls])
39 | end
40 | end
41 |
--------------------------------------------------------------------------------
/lib/scrape/flow/feed.ex:
--------------------------------------------------------------------------------
1 | defmodule Scrape.Flow.Feed do
2 | @moduledoc false
3 |
4 | alias Scrape.Flow
5 | alias Scrape.IR.Feed
6 |
7 | def from_url(url, opts \\ []) do
8 | Flow.start(opts)
9 | |> Flow.assign(url: url)
10 | |> Flow.assign(xml: &Scrape.Source.HTTP.get!(&1[:url]))
11 | |> process_xml()
12 | end
13 |
14 | def from_file(path, opts \\ []) do
15 | Flow.start(opts)
16 | |> Flow.assign(path: path)
17 | |> Flow.assign(url: nil)
18 | |> Flow.assign(xml: &Scrape.Source.Disk.get!(&1[:path]))
19 | |> process_xml()
20 | end
21 |
22 | def from_string(xml, opts \\ []) do
23 | Flow.start(opts)
24 | |> Flow.assign(xml: xml)
25 | |> Flow.assign(url: nil)
26 | |> process_xml()
27 | end
28 |
29 | defp process_xml(%{assigns: %{xml: nil}}), do: {:error, :xml_invalid}
30 |
31 | defp process_xml(%{assigns: %{xml: ""}}), do: {:error, :xml_invalid}
32 |
33 | defp process_xml(flow) do
34 | flow
35 | |> Flow.assign(tree: &Scrape.Tools.Tree.from_xml_string(&1[:xml]))
36 | |> Flow.assign(title: &Feed.title(&1[:tree]))
37 | |> Flow.assign(description: &Feed.description(&1[:tree]))
38 | |> Flow.assign(website_url: &Feed.website_url(&1[:tree]))
39 | |> Flow.assign(items: &items/1)
40 | |> Flow.finish([:url, :title, :description, :website_url, :items])
41 | end
42 |
43 | defp items(%{tree: tree, url: url}) do
44 | tree
45 | |> Feed.items()
46 | |> Enum.map(fn item -> Scrape.Flow.FeedItem.from_tree(item, url) end)
47 | |> Enum.filter(fn {status, _} -> status == :ok end)
48 | |> Enum.map(&elem(&1, 1))
49 | end
50 | end
51 |
--------------------------------------------------------------------------------
/lib/scrape/flow/feed_item.ex:
--------------------------------------------------------------------------------
1 | defmodule Scrape.Flow.FeedItem do
2 | @moduledoc false
3 |
4 | alias Scrape.Flow
5 | alias Scrape.IR.FeedItem, as: Item
6 |
7 | def from_tree(tree, url, opts \\ []) do
8 | Flow.start(opts)
9 | |> Flow.assign(tree: tree)
10 | |> Flow.assign(url: url)
11 | |> Flow.assign(title: &Item.title(&1[:tree]))
12 | |> Flow.assign(description: &Item.description(&1[:tree]))
13 | |> Flow.assign(article_url: &Item.article_url(&1[:tree], &1[:url]))
14 | |> Flow.assign(tags: &Item.tags(&1[:tree]))
15 | |> Flow.assign(author: &Item.author(&1[:tree]))
16 | |> Flow.assign(image_url: &Item.image_url(&1[:tree], &1[:url]))
17 | |> Flow.finish([:title, :description, :article_url, :tags, :author, :image_url])
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/lib/scrape/ir/feed.ex:
--------------------------------------------------------------------------------
1 | defmodule Scrape.IR.Feed do
2 | @moduledoc """
3 | Information Retrieval implementations to extract data from feeds (RSS or Atom).
4 |
5 | Makes intense use of `Scrape.Tools.Tree` and it's functions to operate on
6 | nested maps instead of raw XML documents.
7 | """
8 |
9 | alias Scrape.Tools.Tree
10 | alias Scrape.Tools.URL
11 |
12 | @doc """
13 | Extract the (best) title from the feed.
14 |
15 | ## Example
16 | iex> Feed.title("abc")
17 | "abc"
18 | """
19 |
20 | @spec title(String.t() | map()) :: nil | String.t() | map()
21 |
22 | def title(feed) when is_binary(feed) do
23 | feed |> Tree.from_xml_string() |> title()
24 | end
25 |
26 | def title(feed) when is_map(feed) do
27 | Tree.first(feed, ["rss.channel.title", "feed.title"])
28 | end
29 |
30 | @doc """
31 | Extract the (best) description from the feed.
32 |
33 | ## Example
34 | iex> Feed.description("abc")
35 | "abc"
36 | """
37 |
38 | @spec description(String.t() | map()) :: nil | String.t() | map()
39 |
40 | def description(feed) when is_binary(feed) do
41 | feed |> Tree.from_xml_string() |> description()
42 | end
43 |
44 | def description(feed) when is_map(feed) do
45 | Tree.first(feed, [
46 | "rss.channel.description",
47 | "rss.channel.subtitle",
48 | "feed.description",
49 | "feed.subtitle"
50 | ])
51 | end
52 |
53 | @doc """
54 | Extract the website_url from the feed.
55 |
56 | ## Example
57 | iex> Feed.website_url("")
58 | "http://example.com"
59 | """
60 |
61 | @spec website_url(String.t() | map()) :: nil | String.t() | map()
62 |
63 | def website_url(feed) when is_binary(feed) do
64 | feed |> Tree.from_xml_string() |> website_url()
65 | end
66 |
67 | def website_url(feed) when is_map(feed) do
68 | feed
69 | |> Tree.first(["rss.channel.link", "feed.link.href"])
70 | |> normalize()
71 | end
72 |
73 | defp normalize(nil), do: nil
74 | defp normalize(""), do: nil
75 | defp normalize(url), do: url |> URL.base()
76 |
77 | @doc """
78 | Returns the list of all feed items.
79 |
80 | ## Example
81 | iex> Feed.items("abc")
82 | [%{"title" => "abc"}]
83 | """
84 |
85 | @spec items(String.t() | map()) :: nil | [map()]
86 |
87 | def items(feed) when is_binary(feed) do
88 | feed |> Tree.from_xml_string() |> items()
89 | end
90 |
91 | def items(feed) when is_map(feed) do
92 | Tree.find_all(feed, ["feed.entry", "rss.channel.item"])
93 | end
94 | end
95 |
--------------------------------------------------------------------------------
/lib/scrape/ir/feed_item.ex:
--------------------------------------------------------------------------------
1 | defmodule Scrape.IR.FeedItem do
2 | @moduledoc """
3 | Similar (and used by) `Scrape.IR.Feed`, but has specialized selectors
4 | to extract data from feed items/entries.
5 | """
6 |
7 | alias Scrape.Tools.Tree
8 | alias Scrape.Tools.URL
9 |
10 | @doc """
11 | Extract the (best) title from the feed item.
12 |
13 | ## Example
14 | iex> FeedItem.title("abc")
15 | "abc"
16 | """
17 |
18 | @spec title(String.t() | map()) :: nil | String.t()
19 |
20 | def title(tree) when is_binary(tree) do
21 | tree |> Tree.from_xml_string() |> title()
22 | end
23 |
24 | def title(tree) when is_map(tree) do
25 | tree
26 | |> Tree.first(["title"])
27 | |> normalize_to_string()
28 | end
29 |
30 | @doc """
31 | Extract the (best) description from the feed item.
32 |
33 | ## Example
34 | iex> FeedItem.description("abc")
35 | "abc"
36 | """
37 |
38 | @spec description(String.t() | map()) :: nil | String.t()
39 |
40 | def description(tree) when is_binary(tree) do
41 | tree |> Tree.from_xml_string() |> description()
42 | end
43 |
44 | def description(tree) when is_map(tree) do
45 | tree
46 | |> Tree.first(["description", "summary", "content"])
47 | |> normalize_to_string()
48 | end
49 |
50 | @doc """
51 | Extract the article_url from the feed item.
52 |
53 | ## Example
54 | iex> FeedItem.article_url("")
55 | "http://example.com"
56 |
57 | iex> FeedItem.article_url("", "http://example.com")
58 | "http://example.com/url"
59 | """
60 |
61 | @spec article_url(String.t() | map(), nil | String.t()) :: nil | String.t()
62 |
63 | def article_url(tree, url \\ "")
64 |
65 | def article_url(tree, url) when is_binary(tree) do
66 | tree |> Tree.from_xml_string() |> article_url(url)
67 | end
68 |
69 | def article_url(tree, url) when is_map(tree) do
70 | tree
71 | |> Tree.first(["link.href", "link"])
72 | |> normalize_to_string()
73 | |> normalize_url(url)
74 | end
75 |
76 | @doc """
77 | Extract the possible tags from the feed item.
78 |
79 | ## Example
80 | iex> FeedItem.tags("abc")
81 | ["abc"]
82 |
83 | iex> FeedItem.tags("")
84 | []
85 | """
86 |
87 | @spec tags(String.t() | map()) :: [String.t()]
88 |
89 | def tags(tree) when is_binary(tree) do
90 | tree |> Tree.from_xml_string() |> tags()
91 | end
92 |
93 | def tags(tree) when is_map(tree) do
94 | tree
95 | |> Tree.find("category")
96 | |> List.wrap()
97 | |> Enum.map(&normalize_to_string/1)
98 | |> Enum.reject(&is_nil/1)
99 | |> Enum.map(&Scrape.IR.Text.clean/1)
100 | |> Enum.map(&String.downcase/1)
101 | end
102 |
103 | @doc """
104 | Extract the author from the feed item.
105 |
106 | ## Example
107 | iex> FeedItem.author("abc")
108 | "abc"
109 | """
110 |
111 | @spec author(String.t() | map()) :: nil | String.t()
112 |
113 | def author(tree) when is_binary(tree) do
114 | tree |> Tree.from_xml_string() |> author()
115 | end
116 |
117 | def author(tree) when is_map(tree) do
118 | tree
119 | |> Tree.first(["~creator", "author.name", "author"])
120 | |> normalize_to_string()
121 | end
122 |
123 | @doc """
124 | Extract the image_url from the feed item.
125 |
126 | ## Example
127 | iex> FeedItem.image_url("")
128 | "abc"
129 | """
130 |
131 | @spec image_url(String.t() | map(), nil | String.t()) :: nil | String.t()
132 |
133 | def image_url(tree, url \\ "")
134 |
135 | def image_url(tree, url) when is_binary(tree) do
136 | tree |> Tree.from_xml_string() |> image_url(url)
137 | end
138 |
139 | def image_url(tree, url) when is_map(tree) do
140 | tree
141 | |> Tree.first(["enclosure.url", "media.content"])
142 | |> normalize_to_string()
143 | |> inline_image(tree)
144 | |> normalize_url(url)
145 | end
146 |
147 | defp inline_image(nil, %{"content" => content}) do
148 | rx = ~r/\ssrc=["']*(([^'"\s]+)\.(jpe?g)|(png))["'\s]/i
149 |
150 | case Regex.run(rx, content, capture: :all_but_first) do
151 | [match] when is_binary(match) -> match
152 | [match | _] when is_binary(match) -> match
153 | _ -> nil
154 | end
155 | end
156 |
157 | defp inline_image(img, _), do: img
158 |
159 | # ensure that a value is either a string or nil, but nothing else
160 | defp normalize_to_string(value) when is_binary(value), do: value
161 | defp normalize_to_string(_), do: nil
162 |
163 | # merge an relative url into an absolute url if possible
164 | defp normalize_url(link, url) when is_binary(url), do: URL.merge(link, url)
165 | defp normalize_url(link, _), do: link
166 | end
167 |
--------------------------------------------------------------------------------
/lib/scrape/ir/html.ex:
--------------------------------------------------------------------------------
1 | defmodule Scrape.IR.HTML do
2 | @moduledoc """
3 | Information Retrieval functions for extracting data out of HTML documents.
4 |
5 | Makes extensive use of `Scrape.Tools.DOM` under the hood, so a customized
6 | jQuery-like approach can be taken.
7 | """
8 |
9 | alias Scrape.Tools.DOM
10 | alias Scrape.Tools.URL
11 |
12 | @doc """
13 | Extract the best possible title from a HTML document (string or DOM) or nil.
14 |
15 | ## Examples
16 | iex> HTML.title("")
17 | nil
18 |
19 | iex> HTML.title("abc")
20 | "abc"
21 | """
22 |
23 | @spec title(DOM.dom()) :: nil | String.t()
24 |
25 | @title_queries [
26 | {"meta[property='og:title']", "content"},
27 | {"meta[property='twitter:title']", "content"},
28 | {"h1"},
29 | {"title"}
30 | ]
31 |
32 | def title(dom) do
33 | case DOM.first(dom, @title_queries) do
34 | nil -> nil
35 | match -> strip_suffix(match)
36 | end
37 | end
38 |
39 | defp strip_suffix(value) do
40 | rx = ~r/\s[|-].{1}.+$/
41 |
42 | case String.match?(value, rx) do
43 | true -> value |> String.split(rx) |> List.first()
44 | false -> value
45 | end
46 | end
47 |
48 | @doc """
49 | Extract the best possible description from a HTML document or nil.
50 |
51 | ## Examples
52 | iex> HTML.description("")
53 | nil
54 |
55 | iex> HTML.description("")
56 | "abc"
57 | """
58 |
59 | @spec description(DOM.dom() | String.t()) :: nil | String.t()
60 |
61 | @description_queries [
62 | {"meta[property='og:description']", "content"},
63 | {"meta[name='twitter:description']", "content"},
64 | {"meta[name='description']", "content"}
65 | ]
66 |
67 | def description(dom) do
68 | DOM.first(dom, @description_queries)
69 | end
70 |
71 | @doc """
72 | Attempts to find the best image_url for the website or nil.
73 |
74 | If a root url is given, will transform relative images to absolute urls.
75 |
76 | ## Examples
77 | iex> HTML.image_url("")
78 | nil
79 |
80 | iex> HTML.image_url("")
81 | "img.jpg"
82 | """
83 | @spec image_url(DOM.dom()) :: nil | String.t()
84 | @spec image_url(DOM.dom(), String.t()) :: nil | String.t()
85 |
86 | @image_url_queries [
87 | {"meta[property='og:image']", "content"},
88 | {"meta[name='twitter:image']", "content"}
89 | ]
90 |
91 | def image_url(dom, url \\ "") do
92 | case DOM.first(dom, @image_url_queries) do
93 | nil -> nil
94 | match -> URL.merge(match, url)
95 | end
96 | end
97 |
98 | @doc """
99 | Attempts to find something resembling a favicon url or nil.
100 |
101 | If a root url is given, will transform relative images to absolute urls.
102 |
103 | ## Examples
104 | iex> HTML.icon_url("")
105 | nil
106 |
107 | iex> HTML.icon_url("")
108 | "img.jpg"
109 | """
110 |
111 | @spec icon_url(DOM.dom()) :: nil | String.t()
112 | @spec icon_url(DOM.dom(), String.t()) :: nil | String.t()
113 |
114 | @icon_url_queries [
115 | {"link[rel='apple-touch-icon']", "href"},
116 | {"link[rel='apple-touch-icon-precomposed']", "href"},
117 | {"link[rel='shortcut icon']", "href"},
118 | {"link[rel='icon']", "href"}
119 | ]
120 |
121 | def icon_url(dom, url \\ "") do
122 | case DOM.first(dom, @icon_url_queries) do
123 | nil -> nil
124 | match -> URL.merge(match, url)
125 | end
126 | end
127 |
128 | @doc """
129 | Attempts to fetch all possible feed_urls from the given HTML document.
130 |
131 | ## Examples
132 | iex> HTML.feed_urls("")
133 | []
134 |
135 | iex> HTML.feed_urls("")
136 | ["/feed.rss"]
137 | """
138 |
139 | @spec feed_urls(DOM.dom()) :: [String.t()]
140 | @spec feed_urls(DOM.dom(), String.t()) :: [String.t()]
141 |
142 | def feed_urls(dom, url \\ "") do
143 | list = feed_meta_tag(dom) ++ feed_inline(dom)
144 |
145 | list
146 | |> Enum.filter(&URL.is_http?(&1))
147 | |> Enum.map(&URL.merge(&1, url))
148 | |> Enum.uniq()
149 | end
150 |
151 | defp feed_meta_tag(dom) do
152 | selector = """
153 | link[type='application/rss+xml'],
154 | link[type='application/atom+xml'],
155 | link[rel='alternate']
156 | """
157 |
158 | DOM.attrs(dom, selector, "href")
159 | end
160 |
161 | defp feed_inline(dom) do
162 | rx = ~r{href=['"]([^'"]*(rss|atom|feed|xml)[^'"]*)['"]}
163 | str = Floki.raw_html(dom)
164 | matches = Regex.scan(rx, str, capture: :all_but_first)
165 | Enum.map(matches, &List.first/1)
166 | end
167 |
168 | @doc """
169 | Try to extract the semantically relevant part from a given document.
170 |
171 |
172 | Uses the [Readability](https://hex.pm/packages/readability) algorithm, which
173 | might fail sometimes. Ideally, it returns a single string containing full
174 | sentences. Remember that this method uses a few heuristics that *somehow*
175 | work together nicely in many cases, but nothing more.
176 | """
177 |
178 | def simple(dom) do
179 | try do
180 | dom
181 | |> Floki.raw_html()
182 | |> Readability.article()
183 | |> Readability.readable_html()
184 | |> String.replace(~r/]*>(.*?)<\/a>/, "\\1")
185 | rescue
186 | _ -> nil
187 | end
188 | end
189 |
190 | @doc """
191 | Try to extract the relevant text content from a given document.
192 |
193 | Uses the [Readability](https://hex.pm/packages/readability) algorithm, which
194 | might fail sometimes. Ideally, it returns a single string containing full
195 | sentences. Remember that this method uses a few heuristics that *somehow*
196 | work together nicely in many cases, but nothing more.
197 | """
198 |
199 | @spec content(DOM.dom()) :: nil | String.t()
200 |
201 | def content(dom) do
202 | try do
203 | dom
204 | |> Readability.article()
205 | |> Floki.filter_out("figure")
206 | |> Readability.readable_text()
207 | |> String.replace(~r/\s+/, " ")
208 | |> String.replace(~r/(\s\S+[a-zäöüß]+)([A-ZÄÖÜ]\S+\s)/u, "\\1. \\2")
209 | rescue
210 | _ -> nil
211 | end
212 | end
213 |
214 | @doc """
215 | Convenient fallback function if `content/1` didn't work. Uses `paragraphs/1`
216 | under the hood.
217 | """
218 |
219 | @spec sentences(DOM.dom()) :: nil | String.t()
220 |
221 | def sentences(dom) do
222 | case paragraphs(dom) do
223 | [] -> nil
224 | list -> Enum.join(list, ".\n\n")
225 | end
226 | end
227 |
228 | @doc """
229 | Attempt to find the most meaningful content snippets in the HTML document.
230 |
231 | Can be used as a fallback algorithm if `content/1` did return nil but *some*
232 | text corpus is needed to work with.
233 |
234 | A text paragraph is relevant if it has a minimum amount of characters and
235 | contains any indicators of a sentence-like structure.
236 | Very naive approach, but works surprisingly well so far.
237 | """
238 |
239 | @spec paragraphs(DOM.dom()) :: [String.t()]
240 |
241 | def paragraphs(dom) do
242 | dom
243 | |> Floki.find("article, p, div, body")
244 | |> Enum.map(&Floki.text(&1, deep: false))
245 | |> Enum.map(&Scrape.IR.Text.normalize_whitespace/1)
246 | |> Enum.filter(¶graph_is_relevant?/1)
247 | end
248 |
249 | defp paragraph_is_relevant?(paragraph) do
250 | String.length(paragraph) > 30 &&
251 | String.contains?(paragraph, [". ", "? ", "! ", "\" ", "\", ", ": "])
252 | end
253 | end
254 |
--------------------------------------------------------------------------------
/lib/scrape/ir/text.ex:
--------------------------------------------------------------------------------
1 | defmodule Scrape.IR.Text do
2 | @moduledoc """
3 | Collection of text mining algorithms, like summarization, classification and
4 | clustering.
5 |
6 | Details are hidden within the algorithms, so a clean interface can be provided.
7 | """
8 |
9 | alias Scrape.IR.Text.TFIDF
10 | alias Scrape.Tools.Word
11 |
12 | @doc false
13 | def generate_summary(text) do
14 | # TODO: my markov chain implementation belongs here.
15 | text
16 | end
17 |
18 | @doc """
19 | Dissect a text into sentences, weight their stemmed keywords against each other and
20 | return the 3 semantically most important sentences.
21 | """
22 |
23 | def extract_summary(text, start_words, language \\ :en) do
24 | text
25 | |> TFIDF.generate_database(language)
26 | |> TFIDF.query(start_words)
27 | end
28 |
29 | @doc """
30 | Find out in which natural language the given text is written in.
31 |
32 | Currently only german and (fallback) english are valid results. Uses external
33 | library [Paasaa](https://hex.pm/packages/paasaa).
34 |
35 | ## Example
36 | iex> Scrape.IR.Text.detect_language("the quick brown fox jumps over...")
37 | :en
38 |
39 | iex> Scrape.IR.Text.detect_language("Es ist ein schönes Wetter heute...")
40 | :de
41 | """
42 |
43 | @spec detect_language(String.t()) :: :de | :en
44 |
45 | def detect_language(text) do
46 | case Paasaa.detect(text) do
47 | "deu" -> :de
48 | _ -> :en
49 | end
50 | end
51 |
52 | @doc """
53 | Remove all occurences of javascript from a HTML snippet.
54 |
55 | Uses a regex (!)
56 |
57 | ## Example
58 | iex> Scrape.IR.Text.without_js("ac")
59 | "ac"
60 | """
61 |
62 | @spec without_js(String.t()) :: String.t()
63 |
64 | def without_js(text) do
65 | rx = ~r/