Download and include referenced URL in XML - python

I have an RSS feed to a news source. Amongst the news text and other metadata, the feed also contains an URL reference to the comments section, which can also be in RSS format. I want to download and include the contents of the comments section for each news article. My aim is to create an RSS feed with the articles and the comments for each article included in the RSS, then convert this new RSS in calibre to PDF.
Here is an example XML:
<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<entry>
<author>
<name>Some Author</name>
<uri>http://thenews.com</uri>
</author>
<category term="sports" label="Sports" />
<content type="html">This is the news text.</content>
<id>123abc</id>
<link href="http://thenews.com/article/123abc/comments" />
<updated>2016-04-29T13:44:00+00:00</updated>
<title>The Title</title>
</entry>
<entry>
<author>
<name>Some other Author</name>
<uri>http://thenews.com</uri>
</author>
<category term="sports" label="Sports" />
<content type="html">This is another news text.</content>
<id>123abd</id>
<link href="http://thenews.com/article/123abd/comments" />
<updated>2016-04-29T14:46:00+00:00</updated>
<title>The other Title</title>
</entry>
</feed>
Now I want to replace the <link href="http://thenews.com/article/123abc/comments" /> with the content of the URL. The RSS feed can be fetched by adding a /rss at the end of the URL. So in the end, a single entry would look like this:
<entry>
<author>
<name>Some Author</name>
<uri>http://thenews.com</uri>
</author>
<category term="sports" label="Sports" />
<content type="html">This is the news text.</content>
<id>123abc</id>
<comments>
<comment>
<author>A commenter</author>
<timestamp>2016-04-29T16:00:00+00:00</timestamp>
<text>Cool story, yo!</text>
</comment>
<comment>
<author>Another commenter</author>
<timestamp>2016-04-29T16:01:00+00:00</timestamp>
<text>This is interesting news.</text>
</comment>
</comments>
<updated>2016-04-29T13:44:00+00:00</updated>
<title>The Title</title>
</entry>
I'm open to any programming language. I tried this with python and lxml but couldn't get far. I was able to extract the comments URL and download the comments feed but couldn't replace the actual <link>-tag.
Without having to download the actual RSS, here's how far I've come:
import lxml.etree as et
import urllib2
import re
# These will be downloaded from the RSS feed source when the code works
xmltext = """[The above news feed, too long to paste]"""
commentsRSS = """[The above comments feed]"""
hdr = { 'User-Agent' : 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36'}
article = et.fromstring(xmltext)
for elem in article.xpath('//feed/entry'):
commentsURL = elem.xpath('link/#href')
#request = urllib2.Request(commentsURL[0] + '.rss', headers=hdr)
#comments = urllib2.urlopen(request).read()
comments = commentsRSS
# Now the <link>-tag should be replaced by the comments feed without the <?xml ...> tag

For each <link> element, download XML from the href attribute and then parse the XML into a new Element. Then replace <link> with the corresponding new Element, something like this :
....
article = et.fromstring(xmltext)
ns = {'d': 'http://www.w3.org/2005/Atom'}
for elem in article.xpath('//d:feed/d:entry/d:link', namespaces=ns):
request = urllib2.Request(elem.attrib['href'] + '.rss', headers=hdr)
comments = urllib2.urlopen(request).read()
newElem = et.fromstring(comments)
elem.getparent().replace(elem, newElem)
# print the result
print et.tostring(article)

Related

Xpath gives no results

I want to scrape the TheRegister.com Security section and parse the XML parts into a data structure.
In the Scrapy Shell I've tried:
>>> fetch('https://www.theregister.com/security/headlines.atom')
resulting in response
2020-11-07 09:34:47 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://www.theregister.com/security/headlines.atom> (referer: None)
The response has a body that can be viewed, see a snippet below (I only selected the first couple of lines)
<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
<id>tag:theregister.com,2005:feed/theregister.com/security/</id>
<title>The Register - Security</title>
<link rel="self" type="application/atom+xml" href="https://www.theregister.com/security/headlines.atom"/>
<link rel="alternate" type="text/html" href="https://www.theregister.com/security/"/>
<rights>Copyright © 2020, Situation Publishing</rights>
<author>
<name>Team Register</name>
<email>webmaster#theregister.co.uk</email>
<uri>https://www.theregister.com/odds/about/contact/</uri>
</author>
<icon>https://www.theregister.com/Design/graphics/icons/favicon.png</icon>
<subtitle>Biting the hand that feeds IT — Enterprise Technology News and Analysis</subtitle>
<logo>https://www.theregister.com/Design/graphics/Reg_default/The_Register_r.png</logo>
<updated>2020-11-06T23:58:13Z</updated>
<entry>
<id>tag:theregister.com,2005:story211912</id>
<updated>2020-11-06T23:58:13Z</updated>
<author>
<name>Thomas Claburn</name>
<uri>https://search.theregister.com/?author=Thomas%20Claburn</uri>
</author>
<link rel="alternate" type="text/html" href="https://go.theregister.com/feed/www.theregister.com/2020/11/06/android_encryption_certs/"/>
<title type="html">Let's Encrypt warns about a third of Android devices will from next year stumble over sites that use its certs</title>
<summary type="html" xml:base="https://www.theregister.com/"><h4>Expiration of cross-signed root certificates spells trouble for pre-7.1.1 kit... unless they're using Firefox</h4> <p>Let's Encrypt, a Certificate Authority (CA) that puts the "S" in "HTTPS" for about <a target="_blank" rel="nofollow" href="https://letsencrypt.org/stats/">220m domains</a>, has issued a warning to users of older Android devices that their web surfing may get choppy next year.…</p> <p><!--#include virtual='/data_centre/_whitepaper_textlinks_top.html' --></p></summary>
</entry>
Why can I not parse any data with the regular Xpath method? I've tried:
>>> response.xpath('entry')
[]
>>> response.xpath('/entry')
[]
>>> response.xpath('//entry')
[]
>>> response.xpath('.//entry')
[]
>>> response.xpath('entry/text()')
[]
>>> response.xpath('/entry/text()')
[]
>>> response.xpath('//entry/text()')
[]
>>> response.xpath('.//entry/text()')
[]
All with no luck. Also the other xml-tags, like title, link, author I cannot extract.
TLDR; execute response.selector.remove_namespaces() before running response.xpath()
It essentially means that you are removing xmlns="http://www.w3.org/2005/Atom" from response to write easier XPath.
Alternative, you can register the namespace and change your selectors to include this namespace:
response.selector.register_namespace('n', 'http://www.w3.org/2005/Atom')
response.xpath('//n:entry')
You can read more details here.

Unreachable xml feed entries

I'm working on a python application supposed to make a request on a phonebook search api and format the received data.
The entries are sent back as an xml feed looking like the exemple at the bottom.
I'm using feedparser to split the information.
What I'm struggling with, is the extraction of the e-mail field.
This information is contained under the tag <tel:extra type="email">
I could only make it work to get the value of "type" for the last extra entry.
The one before and the content between the tags are unreachable.
Does anyone have some experience with this kind of feeds?
Thank you for helping me.
API information
Python code:
import feedparser
data = feedparser.parse(xml)
entry = data.entries[0]
print(entry.tel_extra)
XML example:
<?xml version="1.0" encoding="utf-8" ?>
<feed xml:lang="de" xmlns="http://www.w3.org/2005/Atom" xmlns:openSearch="http://a9.com/-/spec/opensearchrss/1.0/" xmlns:tel="http://tel.search.ch/api/spec/result/1.0/">
<id>https://tel.search.ch/api/04b361c38a40dc3aab2355d79f221f86/5acc2bdfc4554dfd5a4bb10424cd597e</id>
<title type="text">tel.search.ch API Search Results</title>
<generator version="1.0" uri="https://tel.search.ch">tel.search.ch</generator>
<updated>2018-02-12T03:00:00Z</updated>
<link href="https://tel.search.ch/result.html?was=nestle&wo=broc&private=0" rel="alternate" type="text/html" />
<link href="http://tel.search.ch/api/?was=nestle&wo=broc&private=0&key=04b361c38a40dc3aab2355d79f221f86" type="application/atom+xml" rel="self" />
<openSearch:totalResults>1</openSearch:totalResults>
<openSearch:startIndex>1</openSearch:startIndex>
<openSearch:itemsPerPage>20</openSearch:itemsPerPage>
<openSearch:Query role="request" searchTerms="nestle broc" startPage="1" />
<openSearch:Image height="1" width="1" type="image/gif">https://www.search.ch/audit/CP/tel/de/api</openSearch:Image>
<entry>
<id>urn:uuid:ca71838ddcbb6a92</id>
<updated>2018-02-12T03:00:00Z</updated>
<published>2018-02-12T03:00:00Z</published>
<title type="text">Nestlé Suisse SA</title>
<content type="text">Nestlé Suisse SA
Fabrique de Broc
rue Jules Bellet 7
1636 Broc/FR
026 921 51 51</content>
<tel:nopromo>*</tel:nopromo>
<author>
<name>tel.search.ch</name>
</author>
<link href="https://tel.search.ch/broc/rue-jules-bellet-7/nestle-suisse-sa" title="Details" rel="alternate" type="text/html" />
<link href="https://tel.search.ch/vcard/Nestle-Suisse-SA.vcf?key=ca71838ddcbb6a92" type="text/x-vcard" title="VCard Download" rel="alternate" />
<link href="https://tel.search.ch/edit/?id=ca71838ddcbb6a92" rel="edit" type="text/html" />
<tel:pos>1</tel:pos>
<tel:id>ca71838ddcbb6a92</tel:id>
<tel:type>Organisation</tel:type>
<tel:name>Nestlé Suisse SA</tel:name>
<tel:occupation>Fabrique de Broc</tel:occupation>
<tel:street>rue Jules Bellet</tel:street>
<tel:streetno>7</tel:streetno>
<tel:zip>1636</tel:zip>
<tel:city>Broc</tel:city>
<tel:canton>FR</tel:canton>
<tel:country>fr</tel:country>
<tel:category>Schokolade</tel:category>
<tel:phone>+41269215151</tel:phone>
<tel:extra type="Fax Service technique">+41269215154</tel:extra>
<tel:extra type="Fax">+41269215525</tel:extra>
<tel:extra type="Besichtigung">+41269215960</tel:extra>
<tel:extra type="email">maisoncailler#nestle.com</tel:extra>
<tel:extra type="website">http://www.cailler.ch</tel:extra>
<tel:copyright>Daten: Swisscom Directories AG</tel:copyright>
</entry>
</feed>
You may want to check out BeautifulSoup.
from bs4 import BeautifulSoup
soup = BeautifulSoup(xml, 'xml')
soup.find("tel:extra", attrs={"type":"email"}).text
Out[111]: 'maisoncailler#nestle.com'

XML Prettifying from file in Python

I have an xml file which looks like the example below.
Many texts contain space as the start character, or have \n (newline) at the beginning, or other crazy stuff. I'm working with xml.etree.ElementTree, and it is good to parse from this file.
But I want more! :) I tried to prettify this mess, but without success. Tried many tutorials, but it always ends without pretty XML.
<?xml version="1.0"?>
<import>
<article>
<name> Name with space
</name>
<source> Daily Telegraph
</source>
<number>72/2015
</number>
<page>10
</page>
<date>2015-03-26
</date>
<author> Tomas First
</author>
<description>Economy
</description>
<attachment>
</attachment>
<region>
</region>
<text>
My text is here
</text>
</article>
<article>
<name> How to parse
</name>
<source> Internet article
</source>
<number>72/2015
</number>
<page>1
</page>
<date>2015-03-26
</date>
<author>Some author
</author>
<description> description
</description>
<attachment>
</attachment>
<region>
</region>
<text>
My text here
</text>
</article>
</import>
When I tried another answers from SO it generates same file or more messy XML
bs4 can do it
from bs4 import BeautifulSoup
doc = BeautifulSoup(xmlstring, 'xml')
print doc.prettify()

Parsing XML in python - stumped how to do this

I've looked through a number of support pages, examples and documents however I am still stumped as to how I can achieve what I am after using python.
I need to process/parse an xml feed and just take very specific values from the XML document. Which is where I am stumped.
The xml looks like the following:
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<feed>
<title type="text">DailyTreasuryYieldCurveRateData</title>
<id></id>
<updated>2014-12-03T07:44:30Z</updated>
<link rel="self" title="DailyTreasuryYieldCurveRateData" href="DailyTreasuryYieldCurveRateData" />
<entry>
<id></id>
<title type="text"></title>
<updated>2014-12-03T07:44:30Z</updated>
<author>
<name />
</author>
<link rel="edit" title="DailyTreasuryYieldCurveRateDatum" href="DailyTreasuryYieldCurveRateData(6235)" />
<category />
<content type="application/xml">
<m:properties>
<d:Id m:type="Edm.Int32">6235</d:Id>
<d:NEW_DATE m:type="Edm.DateTime">2014-12-01T00:00:00</d:NEW_DATE>
<d:BC_1MONTH m:type="Edm.Double">0.01</d:BC_1MONTH>
<d:BC_3MONTH m:type="Edm.Double">0.03</d:BC_3MONTH>
<d:BC_6MONTH m:type="Edm.Double">0.08</d:BC_6MONTH>
<d:BC_1YEAR m:type="Edm.Double">0.13</d:BC_1YEAR>
<d:BC_2YEAR m:type="Edm.Double">0.49</d:BC_2YEAR>
<d:BC_3YEAR m:type="Edm.Double">0.9</d:BC_3YEAR>
<d:BC_5YEAR m:type="Edm.Double">1.52</d:BC_5YEAR>
<d:BC_7YEAR m:type="Edm.Double">1.93</d:BC_7YEAR>
<d:BC_10YEAR m:type="Edm.Double">2.22</d:BC_10YEAR>
<d:BC_20YEAR m:type="Edm.Double">2.66</d:BC_20YEAR>
<d:BC_30YEAR m:type="Edm.Double">2.95</d:BC_30YEAR>
<d:BC_30YEARDISPLAY m:type="Edm.Double">2.95</d:BC_30YEARDISPLAY>
</m:properties>
</content>
</entry>
<entry>
<id></id>
<title type="text"></title>
<updated>2014-12-03T07:44:30Z</updated>
<author>
<name />
</author>
<link rel="edit" title="DailyTreasuryYieldCurveRateDatum" href="DailyTreasuryYieldCurveRateData(6236)" />
<category />
<content type="application/xml">
<m:properties>
<d:Id m:type="Edm.Int32">6236</d:Id>
<d:NEW_DATE m:type="Edm.DateTime">2014-12-02T00:00:00</d:NEW_DATE>
<d:BC_1MONTH m:type="Edm.Double">0.04</d:BC_1MONTH>
<d:BC_3MONTH m:type="Edm.Double">0.03</d:BC_3MONTH>
<d:BC_6MONTH m:type="Edm.Double">0.08</d:BC_6MONTH>
<d:BC_1YEAR m:type="Edm.Double">0.14</d:BC_1YEAR>
<d:BC_2YEAR m:type="Edm.Double">0.55</d:BC_2YEAR>
<d:BC_3YEAR m:type="Edm.Double">0.96</d:BC_3YEAR>
<d:BC_5YEAR m:type="Edm.Double">1.59</d:BC_5YEAR>
<d:BC_7YEAR m:type="Edm.Double">2</d:BC_7YEAR>
<d:BC_10YEAR m:type="Edm.Double">2.28</d:BC_10YEAR>
<d:BC_20YEAR m:type="Edm.Double">2.72</d:BC_20YEAR>
<d:BC_30YEAR m:type="Edm.Double">3</d:BC_30YEAR>
<d:BC_30YEARDISPLAY m:type="Edm.Double">3</d:BC_30YEARDISPLAY>
</m:properties>
</content>
</entry>
</feed>
This XML document gets a new Entry appended each day for the duration of the month when it resets and starts again on the 1st of the next month.
I need to extract the date from d:NEW_DATE and the value from d:BC_10YEAR, now when there is just a single entry this is no problem, however I am struggling to work out how to have it go through the file and extracting the relevant date and value from each ENTRY block.
Any assistance is very much appreciated.
BeautifulSoup is probably the easiest way to do what you're looking for:
from BeautifulSoup import BeautifulSoup
xmldoc = open('datafile.xml', 'r').read()
bs = BeautifulSoup(xmldoc)
entryList = bs.findAll('entry')
for entry in entryList:
print entry.content.find('m:properties').find('d:new_date').contents[0]
print entry.content.find('m:properties').find('d:bc_10year').contents[0]
You can then replace the print with whatever you want to do with the data (add to a list etc.).

Text parsing XML File with Python

So I have been able to query and receive an HTTP RSS webpage, convert it to a .txt file, and query the elements within the XML with minidom.
What I am tying to do next is create a selective list of links that meet my requirements.
Here is an example XML file that has a similar architecture to my file:
<xml>
<Document name = "example_file.txt">
<entry id = "1">
<link href="http://wwww.examplesite.com/files/test_image_1_Big.jpg"/>
</entry>
<entry id = "2">
<link href="http://wwww.examplesite.com/files/test_image_1.jpg"/>
</entry>
<entry id = "3">
<link href="http://wwww.examplesite.com/files/test_image_1_Small.jpg"/>
</entry>
</entry>
<entry id = "4">
<link href="http://wwww.examplesite.com/files/test_image_1.png"/>
</entry>
<entry id = "5">
<link href="http://wwww.examplesite.com/files/test_image_2_Big.jpg"/>
</entry>
<entry id = "6">
<link href="http://wwww.examplesite.com/files/test_image_2.jpg"/>
</entry>
<entry id = "7">
<link href="http://wwww.examplesite.com/files/test_image_2_Small.jpg"/>
</entry>
</entry>
<entry id = "8">
<link href="http://wwww.examplesite.com/files/test_image_2.png"/>
</entry>
</Document>
</xml>
With minidom, I can get it down to a list of just links, but I think I can skip this step if I can create a list based off of text-searching parameters. I do not want all links, I only want these links:
http://wwww.examplesite.com/files/test_image_1.jpg
http://wwww.examplesite.com/files/test_image_2.jpg
Being new to Python, I am not sure how to say "only grab links that do not have ".png", "Big", or "Small" in the link name.
My end goal is to have python download these files one at a time. Would a list be best for this?
To make this even more complicated, I am limited to the stock library with Python 2.6. I won't be able to implement any great 3rd party APIs.
Using lxml and cssselect this is easy:
from pprint import pprint
import cssselect # noqa
from lxml.html import fromstring
doc = fromstring(open("foo.html", "r").read())
links = [e.attrib["href"] for e in doc.cssselect("link")]
pprint(links)
Output:
['http://wwww.examplesite.com/files/test_image_1_Big.jpg',
'http://wwww.examplesite.com/files/test_image_1.jpg',
'http://wwww.examplesite.com/files/test_image_1_Small.jpg',
'http://wwww.examplesite.com/files/test_image_1.png',
'http://wwww.examplesite.com/files/test_image_2_Big.jpg',
'http://wwww.examplesite.com/files/test_image_2.jpg',
'http://wwww.examplesite.com/files/test_image_2_Small.jpg',
'http://wwww.examplesite.com/files/test_image_2.png']
If you only want two of the links (which two?):
links = links[:2]
This is called Slicing in Python.
Being new to Python, I am not sure how to say "only grab links that do not have ".png", "Big", or "Small" in the link name. Any help would be great
You can filter your list like this:
doc = fromstring(open("foo.html", "r").read())
links = [e.attrib["href"] for e in doc.cssselect("link")]
predicate = lambda l: not any([s in l for s in ("png", "Big", "Small")])
links = [l for l in links if predicate(l)]
pprint(links)
This will give you:
['http://wwww.examplesite.com/files/test_image_1.jpg',
'http://wwww.examplesite.com/files/test_image_2.jpg']
import re
from xml.dom import minidom
_xml = '''<?xml version="1.0" encoding="utf-8"?>
<xml >
<Document name="example_file.txt">
<entry id="1">
<link href="http://wwww.examplesite.com/files/test_image_1_Big.jpg"/>
</entry>
<entry id="2">
<link href="http://wwww.examplesite.com/files/test_image_1.jpg"/>
</entry>
<entry id="3">
<link href="http://wwww.examplesite.com/files/test_image_1_Small.jpg"/>
</entry>
<entry id="4">
<link href="http://wwww.examplesite.com/files/test_image_1.png"/>
</entry>
<entry id="5">
<link href="http://wwww.examplesite.com/files/test_image_2_Big.jpg"/>
</entry>
<entry id="6">
<link href="http://wwww.examplesite.com/files/test_image_2.jpg"/>
</entry>
<entry id="7">
<link href="http://wwww.examplesite.com/files/test_image_2_Small.jpg"/>
</entry>
<entry id="8">
<link href="http://wwww.examplesite.com/files/test_image_2.png"/>
</entry>
</Document>
</xml>
'''
doc = minidom.parseString(_xml) # minidom.parse(your-file-path) gets same resul
entries = doc.getElementsByTagName('entry')
link_ref = (
entry.getElementsByTagName('link').item(0).getAttribute('href')
for entry in entries
)
plain_jpg = re.compile(r'.*\.jpg$') # regex you needs
result = (link for link in link_ref if plain_jpg.match(link))
print list(result)
This code gets result of [u'http://wwww.examplesite.com/files/test_image_1_Big.jpg', u'http://wwww.examplesite.com/files/test_image_1.jpg', u'http://wwww.examplesite.com/files/test_image_1_Small.jpg', u'http://wwww.examplesite.com/files/test_image_2_Big.jpg', u'http://wwww.examplesite.com/files/test_image_2.jpg', u'http://wwww.examplesite.com/files/test_image_2_Small.jpg'].
But we may use xml.etree.ElementTree better.
etree is faster and low memory and smarter interfaces.
etree was bundled in standard library.
from feedparse import parse
data=parse("foo.html")
for elem in data['entries']:
if 'link' in elem.keys():
print(elem['link'])
The Library "feedparse" returns dictionaries by parsing the XML content

Categories